require 'encore/entity/input/errors' require 'encore/entity/input/exposed_attributes' require 'encore/entity/input/associations' module Encore module Entity module Input extend ActiveSupport::Concern included do extend ActiveModel::Callbacks include Encore::Entity::Input::ExposedAttributes include Encore::Entity::Input::Associations define_model_callbacks :save # Expose ID as a readonly attribute expose :id, readonly: true # Make the context accessible attr_reader :context end # Assign specific attribute to the object def assign_attributes(attributes = {}, opts = nil) # Find the right context unless @context = opts.try(:delete, :context) @context = @object.persisted? ? :partial_update : :create end # Convert attribute keys to Attribute objects attributes = self.class.map_attributes(self.class, attributes) # Loop through every passed attribute and set it assign_provided_attributes(attributes) # If we're doing an update, for any exposed attribute that wasn't passed, set it to nil reset_forgotten_attributes(attributes) end protected def assign_provided_attributes(attributes) attributes.each_pair do |attribute, value| if self.class.modifiable_attribute?(attribute) attribute.assign(self, value) else raise Encore::Entity::Input::InvalidAttributeError.new("The #{attribute} attribute is not exposed.") end end end def reset_forgotten_attributes(attributes) if @context == :update (self.class.modifiable_attributes - attributes.keys).each do |attribute| attribute.assign(self, nil) end end end module ClassMethods def map_attributes(klass, params) params.inject({}) do |memo, (attribute, value)| exposed_attribute = klass.exposed_attributes.detect { |a| a.attribute == attribute } key = exposed_attribute || Attribute.new(klass, attribute.to_sym) memo.merge key => value end end end end end end