6.0 KiB
ARCHITECTURE
An ActiveModel::Serializer wraps a serializable resource
and exposes an attributes method, among a few others.
It allows you to specify which attributes and associations should be represented in the serializatation of the resource.
It requires an adapter to transform its attributes into a JSON document; it cannot be serialized itself.
It may be useful to think of it as a
presenter.
The ActiveModel::ArraySerializer represent a collection of resources as serializers
and, if there is no serializer, primitives.
The ActiveModel::Adapter describes the structure of the JSON document generated from a
serializer. For example, the Attributes example represents each serializer as its
unmodified attributes. The JsonApi adapter represents the serializer as a JSON
API document.
The ActiveModel::SerializableResource acts to coordinate the serializer(s) and adapter
to an object that responds to to_json, and as_json. It is used in the controller to
encapsulate the serialization resource when rendered. However, it can also be used on its own
to serialize a resource outside of a controller, as well.
Primitive handling
Definitions: A primitive is usually a String or Array. There is no serializer
defined for them; they will be serialized when the resource is converted to JSON (as_json or
to_json). (The below also applies for any object with no serializer.)
ActiveModelSerializers doesn't handle primitives passed to render json: at all.
However, when a primitive value is an attribute or in a collection, it is not modified.
Internally, if no serializer can be found in the controller, the resource is not decorated by ActiveModelSerializers.
If the collection serializer (ArraySerializer) cannot
identify a serializer for a resource in its collection, it raises NoSerializerError
which is rescued in AcitveModel::Serializer::Reflection#build_association which sets
the association value directly:
reflection_options[:virtual_value] = association_value.try(:as_json) || association_value
(which is called by the adapter as serializer.associations(*).)
How options are parsed
High-level overview:
- For a collection
:serializerspecifies the collection serializer and:each_serializerspecifies the serializer for each resource in the collection.
- For a single resource, the
:serializeroption is the resource serializer. - Options are partitioned in serializer options and adapter options. Keys for adapter options are specified by
ADAPTER_OPTION_KEYS. The remaining options are serializer options.
Details:
- ActionController::Serialization
serializable_resource = ActiveModel::SerializableResource.new(resource, options)1.optionsare partitioned intoadapter_optsand everything else (serializer_opts). Theadapter_optskeys are defined inActiveModel::SerializableResource::ADAPTER_OPTION_KEYS.- ActiveModel::SerializableResource
if serializable_resource.serializer?(there is a serializer for the resource, and an adapter is used.) - Whereserializer?isuse_adapter? && !!(serializer)- Where
use_adapter?: 'True when no explicit adapter given, or explicit value is truthy (non-nil); False when explicit adapter is falsy (nil or false)' - Where
serializer:- from explicit
:serializeroption, else - implicitly from resource
ActiveModel::Serializer.serializer_for(resource)
- from explicit
- Where
- A side-effect of checking
serializeris:- The
:serializeroption is removed from the serializer_opts hash - If the
:each_serializeroption is present, it is removed from the serializer_opts hash and set as the:serializeroption
- The
- The serializer and adapter are created as
1.
serializer_instance = serializer.new(resource, serializer_opts)2.adapter_instance = ActiveModel::Serializer::Adapter.create(serializer_instance, adapter_opts) - ActiveModel::Serializer::ArraySerializer#new
- If the
serializer_instancewas aArraySerializerand the:serializerserializer_opts is present, then that serializer is passed into each resource. - ActiveModel::Serializer#attributes is used by the adapter to get the attributes for resource as defined by the serializer.
What does a 'serializable resource' look like?
ActiveModelSerializers provides a
ActiveModelSerializers::Model,
which is a simple serializable PORO (Plain-Old Ruby Object).
ActiveModelSerializers::Model may be used either as a template, or in production code.
class MyModel < ActiveModelSerializers::Model
attr_accessor :id, :name, :level
end
The default serializer for MyModel would be MyModelSerializer whether MyModel is an
ActiveRecord::Base object or not.
Outside of the controller the rules are exactly the same as for records. For example:
render json: MyModel.new(level: 'awesome'), adapter: :json
would be serialized the same as
ActiveModel::SerializableResource.new(MyModel.new(level: 'awesome'), adapter: :json).as_json