This document focuses on architecture the 0.10.x version of ActiveModelSerializers. If you are interested in the architecture of the 0.8 or 0.9 versions, please refer to the 0.8 README or 0.9 README.
The original design is also available here.
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::CollectionSerializer
represents 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 ActiveModelSerializers::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.
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 (CollectionSerializer) cannot
identify a serializer for a resource in its collection, it throws :no_serializer
.
For example, when caught by Reflection#build_association
, the association value is set directly:
reflection_options[:virtual_value] = association_value.try(:as_json) || association_value
(which is called by the adapter as serializer.associations(*)
.)
High-level overview:
- For a collection
:serializer
specifies the collection serializer and:each_serializer
specifies the serializer for each resource in the collection.
- For a single resource, the
:serializer
option 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 = ActiveModelSerializers::SerializableResource.new(resource, options)
1.options
are partitioned intoadapter_opts
and everything else (serializer_opts
). Theadapter_opts
keys are defined inActiveModelSerializers::SerializableResource::ADAPTER_OPTION_KEYS
.- ActiveModelSerializers::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
:serializer
option, else - implicitly from resource
ActiveModel::Serializer.serializer_for(resource)
- from explicit
- Where
- A side-effect of checking
serializer
is:- The
:serializer
option is removed from the serializer_opts hash - If the
:each_serializer
option is present, it is removed from the serializer_opts hash and set as the:serializer
option
- 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::CollectionSerializer#new
- If the
serializer_instance
was aCollectionSerializer
and the:serializer
serializer_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.
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
ActiveModelSerializers::SerializableResource.new(MyModel.new(level: 'awesome'), adapter: :json).as_json