Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promote important architecture description that answers a lot of questions we get #1994

Merged
merged 1 commit into from
Dec 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 135 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,141 @@ serializer = SomeSerializer.new(resource, serializer_options)
serializer.attributes
serializer.associations
```
See [ARCHITECTURE.md](docs/ARCHITECTURE.md) for more information.

## 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](https://github.com/rails-api/active_model_serializers/blob/0-8-stable/README.md) or
[0.9 README](https://github.com/rails-api/active_model_serializers/blob/0-9-stable/README.md).

The original design is also available [here](https://github.com/rails-api/active_model_serializers/blob/d72b66d4c5355b0ff0a75a04895fcc4ea5b0c65e/README.textile).

### ActiveModel::Serializer

An **`ActiveModel::Serializer`** wraps a [serializable resource](https://github.com/rails/rails/blob/4-2-stable/activemodel/lib/active_model/serialization.rb)
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](http://blog.steveklabnik.com/posts/2011-09-09-better-ruby-presenters).

#### ActiveModel::CollectionSerializer
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we can link to these.


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](http://jsonapi.org/) 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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could use some attention to rewrite, but I didn't want to expand the scope here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at least this is something easily point-to-able from people wanting to serialize hashes and such


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`](https://github.com/rails-api/active_model_serializers/issues/1191#issuecomment-142327128).
For example, when caught by `Reflection#build_association`, and the association value is set directly:

```ruby
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`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/serializable_resource.rb#L5).
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`](lib/active_model_serializers/serializable_resource.rb#L5).
1. **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)`
1. 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
1. 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)`
1. **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](https://github.com/rails-api/active_model_serializers/blob/a54d237e2828fe6bab1ea5dfe6360d4ecc8214cd/lib/active_model/serializer/array_serializer.rb#L14-L16).
1. **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](http://www.rubydoc.info/github/rails-api/active_model_serializers/ActiveModel/Serializer/Lint/Tests)
[code](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer/lint.rb).

ActiveModelSerializers provides a
[`ActiveModelSerializers::Model`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/model.rb),
which is a simple serializable PORO (Plain-Old Ruby Object).

`ActiveModelSerializers::Model` may be used either as a reference implementation, or in production code.

```ruby
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:

```ruby
render json: MyModel.new(level: 'awesome'), adapter: :json
```

would be serialized the same as

```ruby
ActiveModelSerializers::SerializableResource.new(MyModel.new(level: 'awesome'), adapter: :json).as_json
```

## Semantic Versioning

Expand Down
125 changes: 0 additions & 125 deletions docs/ARCHITECTURE.md

This file was deleted.

1 change: 0 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ This is the documentation of ActiveModelSerializers, it's focused on the **0.10.
- JSON API
- [Schema](jsonapi/schema.md)
- [Errors](jsonapi/errors.md)
- [ARCHITECTURE](ARCHITECTURE.md)

## How to

Expand Down
19 changes: 2 additions & 17 deletions docs/general/rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,11 @@ render json: @posts, serializer: CollectionSerializer, each_serializer: PostPrev

## Serializing non-ActiveRecord objects

All serializable resources must pass the
[ActiveModel::Serializer::Lint::Tests](../../lib/active_model/serializer/lint.rb#L17).

See the ActiveModelSerializers::Model for a base class that implements the full
API for a plain-old Ruby object (PORO).
See [README](../../README.md#what-does-a-serializable-resource-look-like)

## SerializableResource options

The `options` hash passed to `render` or `ActiveModelSerializers::SerializableResource.new(resource, options)`
are partitioned into `serializer_opts` and `adapter_opts`. `adapter_opts` are passed to new Adapters;
`serializer_opts` are passed to new Serializers.

The `adapter_opts` are specified in [ActiveModelSerializers::SerializableResource::ADAPTER_OPTIONS](../../lib/active_model_serializers/serializable_resource.rb#L5).
The `serializer_opts` are the remaining options.

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

See [ARCHITECTURE](../ARCHITECTURE.md) for more information.
See [README](../../README.md#activemodelserializersserializableresource)

### adapter_opts

Expand Down