diff --git a/lib/mapper/api/default/factory.rb b/lib/mapper/api/default/factory.rb index b0540e9..0ce04a2 100644 --- a/lib/mapper/api/default/factory.rb +++ b/lib/mapper/api/default/factory.rb @@ -2,6 +2,7 @@ require_relative '../factory' require_relative '../../mixin/errors' +require_relative '../../path/segment' module AMA module Entity @@ -17,8 +18,8 @@ class Factory < API::Factory # @param [AMA::Entity::Mapper::Type] type # @param [Object] _data # @param [AMA::Entity::Mapper::Context] context - def create(type, _data = nil, context = nil) - type.type.new + def create(type, _data, context) + create_internal(type) rescue StandardError => e message = "Failed to instantiate #{type} directly from class" if e.is_a?(ArgumentError) @@ -26,6 +27,19 @@ def create(type, _data = nil, context = nil) end mapping_error(message, parent: e, context: context) end + + private + + def create_internal(type) + entity = type.type.new + type.attributes.values.each do |attribute| + next if attribute.default.nil? || attribute.virtual + segment = Path::Segment.attribute(attribute.name) + value = attribute.default + type.injector.inject(entity, type, attribute, value, segment) + end + entity + end end end end diff --git a/test/suite/integration/mapper/api/default/factory.spec.rb b/test/suite/integration/mapper/api/default/factory.spec.rb new file mode 100644 index 0000000..28276c3 --- /dev/null +++ b/test/suite/integration/mapper/api/default/factory.spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require_relative '../../../../../../lib/mapper/api/default/factory' +require_relative '../../../../../../lib/mapper/exception/mapping_error' + +klass = ::AMA::Entity::Mapper::API::Default::Factory +mapping_error_class = ::AMA::Entity::Mapper::Exception::MappingError + +describe klass do + let(:factory) do + klass.new + end + + let(:context) do + double( + path: double + ) + end + + let(:type) do + double( + type: double, + attributes: { + counter: double( + name: :counter, + default: 0, + virtual: false + ), + value: double( + name: :value, + default: nil, + virtual: false + ), + _element: double( + name: :_element, + default: {}, + virtual: true + ) + }, + injector: double(inject: nil) + ) + end + + describe '#create' do + it 'instantiates class by issuing direct #new() call' do + value = double + allow(type).to receive(:attributes).and_return({}) + expect(type.type).to receive(:new).and_return(value) + expect(factory.create(type, double, context)).to eq(value) + end + + it 'wraps constructor errors' do + expect(type.type).to receive(:new).and_raise + proc = lambda do + factory.create(type, double, context) + end + expect(&proc).to raise_error(mapping_error_class) + end + + it 'reports invalid method signature in case of argument error' do + expect(type.type).to receive(:new).and_raise(ArgumentError) + proc = lambda do + factory.create(type, double, context) + end + expect(&proc).to raise_error(mapping_error_class, /initialize|constructor/) + end + + it 'fills entity with default attribute values' do + dummy = double + expect(type.type).to receive(:new).and_return(dummy) + + attribute = type.attributes[:counter] + match = receive(:inject).with(dummy, type, attribute, 0, anything) + expect(type.injector).to(match) + + attribute = type.attributes[:value] + match = receive(:inject).with(dummy, type, attribute, anything, anything) + expect(type.injector).not_to(match) + + factory.create(type, double, context) + end + + it 'ignores virtual attributes' do + dummy = double + expect(type.type).to receive(:new).and_return(dummy) + + attribute = type.attributes[:_element] + match = receive(:inject).with(dummy, type, attribute, anything, anything) + expect(type.injector).not_to(match) + + factory.create(type, double, context) + end + end +end diff --git a/test/suite/integration/mapper/api/wrapper/factory.spec.rb b/test/suite/integration/mapper/api/wrapper/factory.spec.rb index 4f1a699..19229fc 100644 --- a/test/suite/integration/mapper/api/wrapper/factory.spec.rb +++ b/test/suite/integration/mapper/api/wrapper/factory.spec.rb @@ -8,11 +8,15 @@ describe klass do let(:type) do - double(type: double) + double( + type: double, + attributes: {}, + injector: double(inject: nil) + ) end describe '#create' do - it 'should provide fallback to default factory' do + it 'provides fallback to default factory' do result = double expect(type.type).to receive(:new).exactly(:once).and_return(result) factory = double @@ -22,7 +26,7 @@ expect(klass.new(factory).create(type)).to eq(result) end - it 'should wrap exceptions in mapping error' do + it 'wraps exceptions in mapping error' do factory = double expect(factory).to receive(:create).exactly(:once).and_raise proc = lambda do @@ -31,11 +35,10 @@ expect(&proc).to raise_error(mapping_error_class) end - it 'should provide hint about method signature' do - factory_class = Class.new do - def create; end - end - factory = factory_class.new + it 'provides hint about method signature' do + error_class = ArgumentError + factory = double + expect(factory).to receive(:create).exactly(:once).and_raise(error_class) proc = lambda do klass.new(factory).create(type) end diff --git a/test/suite/integration/mapper/type/concrete.spec.rb b/test/suite/integration/mapper/type/concrete.spec.rb index ea344cb..97c200a 100644 --- a/test/suite/integration/mapper/type/concrete.spec.rb +++ b/test/suite/integration/mapper/type/concrete.spec.rb @@ -124,10 +124,10 @@ def self.to_s end describe '#factory' do - it 'should provide default factory if none was set' do + it 'provides default factory if none was set' do type = klass.new(dummy_class) factory = type.factory - expect(factory.create(type)).to be_a(dummy_class) + expect(factory.create(type, double, double)).to be_a(dummy_class) end end diff --git a/test/suite/unit/mapper/api/default/factory.spec.rb b/test/suite/unit/mapper/api/default/factory.spec.rb deleted file mode 100644 index 737b22b..0000000 --- a/test/suite/unit/mapper/api/default/factory.spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../../../../../lib/mapper/api/default/factory' -require_relative '../../../../../../lib/mapper/exception/mapping_error' - -klass = ::AMA::Entity::Mapper::API::Default::Factory -mapping_error_class = ::AMA::Entity::Mapper::Exception::MappingError - -describe klass do - let(:factory) do - klass.new - end - - describe '#create' do - it 'should instantiate class by issuing direct #new() call' do - value = double - type = double(type: double) - expect(type.type).to receive(:new).and_return(value) - expect(factory.create(type)).to eq(value) - end - - it 'should raise mapping error in case of faulty constructor' do - type = double(type: double) - expect(type.type).to receive(:new).and_raise - proc = lambda do - factory.create(type) - end - expect(&proc).to raise_error(mapping_error_class) - end - - it 'should include information about invalid method signature in case of argument error' do - type = double(type: double) - expect(type.type).to receive(:new).and_raise(ArgumentError) - proc = lambda do - factory.create(type) - end - expect(&proc).to raise_error(mapping_error_class, /initialize|constructor/) - end - end -end