Skip to content

Latest commit

 

History

History
126 lines (96 loc) · 6.54 KB

ARCHITECTURE.md

File metadata and controls

126 lines (96 loc) · 6.54 KB

Back to Guides

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.

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 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.

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 ActiveModel::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
    • :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:

  1. ActionController::Serialization
  2. serializable_resource = ActiveModelSerializers::SerializableResource.new(resource, options) 1. options are partitioned into adapter_opts and everything else (serializer_opts). The adapter_opts keys are defined in ActiveModelSerializers::SerializableResource::ADAPTER_OPTION_KEYS.
  3. ActiveModelSerializers::SerializableResource
  4. if serializable_resource.serializer? (there is a serializer for the resource, and an adapter is used.) - Where serializer? is use_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:
      1. from explicit :serializer option, else
      2. implicitly from resource ActiveModel::Serializer.serializer_for(resource)
  5. 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
  6. 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)
  7. ActiveModel::Serializer::ArraySerializer#new
  8. If the serializer_instance was a ArraySerializer and the :serializer serializer_opts is present, then that serializer is passed into each resource.
  9. 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?

  • An ActiveRecord::Base object.
  • Any Ruby object that passes the Lint code.

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