Skip to content
This repository has been archived by the owner on Jan 3, 2024. It is now read-only.

Latest commit

 

History

History
120 lines (92 loc) · 5.95 KB

ARCHITECTURE.md

File metadata and controls

120 lines (92 loc) · 5.95 KB

Back to Guides

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
    • :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_OPTIONS. The remaining options are serializer options.

Details:

  1. ActionController::Serialization
  2. serializable_resource = ActiveModel::SerializableResource.new(resource, options) 1. options are partitioned into adapter_opts and everything else (serializer_opts). The adapter options keys for the are defined by ADAPTER_OPTIONS.
  3. ActiveModel::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 at passes or otherwise 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

ActiveModel::SerializableResource.new(MyModel.new(level: 'awesome'), adapter: :json).as_json