module ActiveModelSerializerPlus::Assignment
@author Todd Knarr <tknarr@silverglass.org>
Default implementation of the #attributes=
method for ActiveModel classes.
This is the base of the JSON and Xml modules. It supplies a standard #attributes=
method to classes that follows the basic pattern of the custom one you’d normally write and adds the ability to pick up object type information from the #attributes_types
hash to convert the hash values of contained objects into actual objects. This mostly eliminates the need to write custom #attributes=
methods for each class with code to check attribute names and initialize objects from child hashes.
The standard ActiveModel/ActiveRecord serialization/deserialization has some flaws, for instance it will serialize classes as contained hashes whether or not they have an #attributes=
method available to deserialize them. We work around that by having building Procs which can do the correct thing in many cases.
Public Instance Methods
The default #attributes=
method which assigns a hash to the object’s attributes. @param [Hash] hash the hash of attribute names and values @return [self] self @raise [ArgumentError] if any error occurs
# File lib/active_model_serializer_plus/assignment.rb, line 148 def attributes=(hash) hash.each do |key, value| # Check #attribute_types for what type this item should be, and if we have an entry # convert it to an actual Class object we can use. attr_klass = nil attr_typename = nil attr_typename = self.attribute_types[key] if self.respond_to?(:attribute_types) && self.attribute_types.is_a?(Hash) if attr_typename.is_a?(Array) # Container container_klass = nil object_klass = nil if attr_typename.length != 2 raise ArgumentError, "Container type specification for attribute #{key.to_s} is invalid." end # Sort out the type of container. First make sure the container type name translates to 'Container'. # If the type name actually is 'Container', take the type of the container from the type of the # original value. If it's an actual type name, convert it to a Class object. Raise exceptions if # any problems are encountered. container_typename = attr_typename[0] if container_typename == 'Container' container_klass = value.class else xlated_container_typename = ActiveModelSerializerPlus.type_name_xlate(container_typename) if xlated_container_typename != 'Container' raise ArgumentError, "Container type #{container_typename} for attribute #{key.to_s} is not a container." end begin container_klass = ActiveModelSerializerPlus.to_class(container_typename) rescue ArgumentError raise ArgumentError, "Container type #{container_typename} for attribute #{key.to_s} is not a valid type." end end # Sort out the type of objects in the container. Convert the object type name to a Class object # and raise an exception if it can't be converted. object_typename = attr_typename[1] begin object_klass = ActiveModelSerializerPlus.to_class(object_typename) rescue ArgumentError raise ArgumentError, "Object type #{object_typename} for attribute #{key.to_s} is not a valid type." end container = container_klass.new if container.nil? raise ArgumentError, "Cannot create container of type #{container_klass.name}." end adder_proc = ActiveModelSerializerPlus.get_container_adder(container_klass.name) iterator_proc = ActiveModelSerializerPlus.get_container_iterator(value.class.name) if adder_proc.nil? || iterator_proc.nil? msg = '' if iterator_proc.nil? msg << "iterate through #{value.class.name}" end if adder_proc.nil? if msg.length == 0 msg << ', ' end msg << "add to #{container_klass.name}" end raise ArgumentError, "Cannot #{msg}." end # Build the new container by iterating through the value container and adding each item to # the new container, using the procs we looked up. iterator_proc.call(container, adder_proc, object_klass, value) self.send("#{key}=", container) else # Object unless attr_typename.nil? begin attr_klass = ActiveModelSerializerPlus.to_class(attr_typename) rescue ArgumentError raise ArgumentError, "Type #{attr_typename.to_s} for attribute #{key.to_s} is not a valid type." end v = ActiveModelSerializerPlus.build_object(attr_klass, value) value = v unless v.nil? end self.send("#{key}=", value) end self end end