ActiveModel::Serializer implementation and Rails hooks
Ruby Other
Latest commit 0422a1e Dec 7, 2016 @NullVoxPopuli NullVoxPopuli committed on GitHub Swap out KeyTransform for CaseTransform (#1993)
* delete KeyTransform, use CaseTransform

* added changelog

README.md

ActiveModelSerializers

Build Status Build Status Build status
Code Quality Code Quality codebeat Test Coverage
Issue Stats Pulse

About

ActiveModelSerializers brings convention over configuration to your JSON generation.

ActiveModelSerializers works through two components: serializers and adapters.

Serializers describe which attributes and relationships should be serialized.

Adapters describe how attributes and relationships should be serialized.

SerializableResource co-ordinates the resource, Adapter and Serializer to produce the resource serialization. The serialization has the #as_json, #to_json and #serializable_hash methods used by the Rails JSON Renderer. (SerializableResource actually delegates these methods to the adapter.)

By default ActiveModelSerializers will use the Attributes Adapter (no JSON root). But we strongly advise you to use JsonApi Adapter, which follows 1.0 of the format specified in jsonapi.org/format. Check how to change the adapter in the sections below.

0.10.x is not backward compatible with 0.9.x nor 0.8.x.

0.10.x is based on the 0.8.0 code, but with a more flexible architecture. We'd love your help. Learn how you can help here.

It is generally safe and recommended to use the master branch.

Installation

Add this line to your application's Gemfile:

gem 'active_model_serializers', '~> 0.10.0'

And then execute:

$ bundle

Getting Started

See Getting Started for the nuts and bolts.

More information is available in the Guides and High-level behavior.

Getting Help

If you find a bug, please report an Issue and see our contributing guide.

If you have a question, please post to Stack Overflow.

If you'd like to chat, we have a community slack.

Thanks!

Documentation

High-level behavior

Choose an adapter from adapters:

ActiveModelSerializers.config.adapter = :json_api # Default: `:attributes`

Given a serializable model:

# either
class SomeResource < ActiveRecord::Base
  # columns: title, body
end
# or
class SomeResource < ActiveModelSerializers::Model
  attributes :title, :body
end

And initialized as:

resource = SomeResource.new(title: 'ActiveModelSerializers', body: 'Convention over configuration')

Given a serializer for the serializable model:

class SomeSerializer < ActiveModel::Serializer
  attribute :title, key: :name
  attributes :body
end

The model can be serialized as:

options = {}
serialization = ActiveModelSerializers::SerializableResource.new(resource, options)
serialization.to_json
serialization.as_json

SerializableResource delegates to the adapter, which it builds as:

adapter_options = {}
adapter = ActiveModelSerializers::Adapter.create(serializer, adapter_options)
adapter.to_json
adapter.as_json
adapter.serializable_hash

The adapter formats the serializer's attributes and associations (a.k.a. includes):

serializer_options = {}
serializer = SomeSerializer.new(resource, serializer_options)
serializer.attributes
serializer.associations

Architecture

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

ActiveModel::Serializer

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.

ActiveModel::CollectionSerializer

The ActiveModel::CollectionSerializer represents a collection of resources as serializers and, if there is no serializer, primitives.

ActiveModelSerializers::Adapter::Base

The ActiveModelSerializeres::Adapter::Base 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.

ActiveModelSerializers::SerializableResource

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.

Internally, if no serializer can be found in the controller, the resource is not decorated by ActiveModelSerializers.

  • However, when a primitive value is an attribute or in a collection, it is not modified.

When serializing a collection and 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, and 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(*).)

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
    1. 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.
  2. ActiveModelSerializers::SerializableResource
    1. 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)
    2. 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
    3. 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)
  3. ActiveModel::Serializer::CollectionSerializer#new
    1. If the serializer_instance was a CollectionSerializer and the :serializer serializer_opts is present, then that serializer is passed into each resource.
  4. ActiveModel::Serializer#attributes is used by the adapter to get the attributes for resource as defined by the serializer.

(In Rails, the options are also passed to the as_json(options) or to_json(options) methods on the resource serialization by the Rails JSON renderer. They are, therefore, important to know about, but not part of ActiveModelSerializers.)

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 reference implementation, or in production code.

class MyModel < ActiveModelSerializers::Model
  attributes :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

Semantic Versioning

This project adheres to semver

Contributing

See CONTRIBUTING.md