Module: ActiveModelSerializerPlus::Assignment

Defined in:
lib/active_model_serializer_plus/assignment.rb

Overview

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.

Author:

Instance Method Summary collapse

Instance Method Details

#attributes=(hash) ⇒ self

The default #attributes= method which assigns a hash to the object’s attributes.

Parameters:

  • hash (Hash)

    the hash of attribute names and values

Returns:

  • (self)

    self

Raises:

  • (ArgumentError)

    if any error occurs



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# 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