From 422a07f678f369c64525a7b5a2982f5715540191 Mon Sep 17 00:00:00 2001 From: Piotr Solnica Date: Wed, 4 Mar 2020 10:16:17 +0100 Subject: [PATCH] Add Processor#merge which allows merging schemas Refs dry-rb/dry-validation#593 --- lib/dry/schema/dsl.rb | 14 ++++ lib/dry/schema/processor.rb | 11 +++ spec/unit/dry/schema/processor/merge_spec.rb | 82 ++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 spec/unit/dry/schema/processor/merge_spec.rb diff --git a/lib/dry/schema/dsl.rb b/lib/dry/schema/dsl.rb index ea0b02a5..68e1ad79 100644 --- a/lib/dry/schema/dsl.rb +++ b/lib/dry/schema/dsl.rb @@ -202,6 +202,20 @@ def call processor_type.new(schema_dsl: self, steps: result_steps) end + # Merge with another dsl + # + # @return [DSL] + # + # @api private + def merge(other) + new( + macros: macros + other.macros, + types: types.merge(other.types), + steps: steps.merge(other.steps), + parent: (parents + other.parents).uniq + ) + end + # Cast this DSL into a rule object # # @return [RuleApplier] diff --git a/lib/dry/schema/processor.rb b/lib/dry/schema/processor.rb index 7bb6acac..b69a235f 100644 --- a/lib/dry/schema/processor.rb +++ b/lib/dry/schema/processor.rb @@ -90,6 +90,17 @@ def call(input) end alias_method :[], :call + # Merge with another schema + # + # @param [Processor] other + # + # @return [Processor, Params, JSON] + # + # @api public + def merge(other) + schema_dsl.merge(other.schema_dsl).() + end + # Return a proc that acts like a schema object # # @return [Proc] diff --git a/spec/unit/dry/schema/processor/merge_spec.rb b/spec/unit/dry/schema/processor/merge_spec.rb new file mode 100644 index 00000000..f20e785f --- /dev/null +++ b/spec/unit/dry/schema/processor/merge_spec.rb @@ -0,0 +1,82 @@ +require 'dry/schema/processor' + +RSpec.describe Dry::Schema::Processor, '#merge' do + context 'without parents' do + subject(:schema) { left.merge(right) } + + let(:left) do + Dry::Schema.define do + after(:rule_applier) do |result| + result.output[:left] = true + end + + required(:name).filled(:string) + end + end + + let(:right) do + Dry::Schema.define do + after(:rule_applier) do |result| + result.output[:right] = true + end + + required(:age).value(Types::Params::Integer) + end + end + + it 'maintains rules' do + expect(schema.(name: '', age: 'foo').errors.to_h).to eql( + name: ['must be filled'], age: ['must be an integer'] + ) + end + + it 'maintains types' do + expect(schema.(name: '', age: '36').errors.to_h).to eql( + name: ['must be filled'] + ) + end + + it 'maintains hooks' do + expect(schema.(name: 'Jane', age: 36).to_h).to eql( + name: 'Jane', age: 36, left: true, right: true + ) + end + end + + context 'with parents' do + subject(:schema) { left.merge(right) } + + let(:left_parent) do + Dry::Schema.define do + required(:email).filled(:string) + end + end + + let(:left) do + Dry::Schema.define(parent: left_parent) do + required(:name).filled(:string) + end + end + + let(:right_parent) do + Dry::Schema.define do + required(:address).filled(:string) + end + end + + let(:right) do + Dry::Schema.define(parent: right_parent) do + required(:age).value(:integer) + end + end + + it 'maintains all rules' do + expect(schema.(name: '', age: 'foo').errors.to_h).to eql( + name: ['must be filled'], + age: ['must be an integer'], + email: ['is missing'], + address: ['is missing'] + ) + end + end +end