diff --git a/.ruby-version b/.ruby-version index 8e8299d..73462a5 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.4.2 +2.5.1 diff --git a/lib/clean_architecture/builders/abstract_active_record_entity_builder.rb b/lib/clean_architecture/builders/abstract_active_record_entity_builder.rb new file mode 100644 index 0000000..923cca3 --- /dev/null +++ b/lib/clean_architecture/builders/abstract_active_record_entity_builder.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module CleanArchitecture + module Builders + # Helps to take an instance of an AR model and wrap it up in the given Entity + # Any columns from the AR model that do not directly map to an attribute on the Entity + # can be specified by overriding #attributes_for_entity. + class AbstractActiveRecordEntityBuilder + # @param [Class] A Dry::Struct based entity that this builder will construct instances of + def self.acts_as_builder_for_entity(entity_class) + define_method :entity_class do + entity_class + end + + private :entity_class + end + + # @param [ActiveRecord::Base] An ActiveRecord model to map to the entity + def initialize(ar_model_instance) + @ar_model_instance = ar_model_instance + end + + def build + entity_class.new(all_attributes_for_entity) + end + + private + + attr_reader :ar_model_instance + + def entity_attribute_names + @entity_attributes ||= entity_class.schema.keys + end + + def ar_attributes_for_entity + attributes_for_entity = @ar_model_instance.attributes + symbolized_attributes_for_entity = Hash[attributes_for_entity.map{|(key, value)| [key.to_sym, value]}] + symbolized_attributes_for_entity.slice(*entity_attribute_names) + end + + def attributes_for_entity + {} + end + + def all_attributes_for_entity + ar_attributes_for_entity.merge(attributes_for_entity) + end + end + end +end diff --git a/spec/lib/clean_architecture/builders/abstract_active_record_entity_builder_spec.rb b/spec/lib/clean_architecture/builders/abstract_active_record_entity_builder_spec.rb new file mode 100644 index 0000000..27f756d --- /dev/null +++ b/spec/lib/clean_architecture/builders/abstract_active_record_entity_builder_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'clean_architecture/builders/abstract_active_record_entity_builder' + +module CleanArchitecture + module Builders + describe AbstractActiveRecordEntityBuilder do + class ExampleModel + def age + 25 + end + + def attributes + { + 'forename' => 'Samuel', + 'surname' => 'Giles', + 'age' => 25 + } + end + end + + class ExampleEntitySchema + def keys + [:forename, :surname, :years_on_planet_earth] + end + end + + class ExampleEntity + def initialize(attributes) + @attributes = attributes + end + + def self.schema + ExampleEntitySchema.new + end + end + + class ExampleBuilder < AbstractActiveRecordEntityBuilder + acts_as_builder_for_entity ExampleEntity + + def attributes_for_entity + { + years_on_planet_earth: ar_model_instance.age + } + end + end + + let(:builder) { ExampleBuilder.new(ar_model_instance) } + let(:ar_model_instance) { ExampleModel.new } + + describe '#build' do + subject(:build) { builder.build } + + let(:example_entity) { instance_double(ExampleEntity) } + + before do + expect(ExampleEntity) + .to receive(:new) + .with( + forename: 'Samuel', + surname: 'Giles', + years_on_planet_earth: 25 + ) + .and_return(example_entity) + end + + it { is_expected.to eq example_entity } + end + end + end +end