Skip to content

Commit fca2d45

Browse files
authored
Merge pull request #1994 from bf4/promote_architecture
Promote important architecture description that answers a lot of questions we get
2 parents 847f50a + 15a8f2c commit fca2d45

File tree

4 files changed

+137
-144
lines changed

4 files changed

+137
-144
lines changed

Diff for: README.md

+135-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,141 @@ serializer = SomeSerializer.new(resource, serializer_options)
156156
serializer.attributes
157157
serializer.associations
158158
```
159-
See [ARCHITECTURE.md](docs/ARCHITECTURE.md) for more information.
159+
160+
## Architecture
161+
162+
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,
163+
please refer to the [0.8 README](https://github.com/rails-api/active_model_serializers/blob/0-8-stable/README.md) or
164+
[0.9 README](https://github.com/rails-api/active_model_serializers/blob/0-9-stable/README.md).
165+
166+
The original design is also available [here](https://github.com/rails-api/active_model_serializers/blob/d72b66d4c5355b0ff0a75a04895fcc4ea5b0c65e/README.textile).
167+
168+
### ActiveModel::Serializer
169+
170+
An **`ActiveModel::Serializer`** wraps a [serializable resource](https://github.com/rails/rails/blob/4-2-stable/activemodel/lib/active_model/serialization.rb)
171+
and exposes an `attributes` method, among a few others.
172+
It allows you to specify which attributes and associations should be represented in the serializatation of the resource.
173+
It requires an adapter to transform its attributes into a JSON document; it cannot be serialized itself.
174+
It may be useful to think of it as a
175+
[presenter](http://blog.steveklabnik.com/posts/2011-09-09-better-ruby-presenters).
176+
177+
#### ActiveModel::CollectionSerializer
178+
179+
The **`ActiveModel::CollectionSerializer`** represents a collection of resources as serializers
180+
and, if there is no serializer, primitives.
181+
182+
### ActiveModelSerializers::Adapter::Base
183+
184+
The **`ActiveModelSerializeres::Adapter::Base`** describes the structure of the JSON document generated from a
185+
serializer. For example, the `Attributes` example represents each serializer as its
186+
unmodified attributes. The `JsonApi` adapter represents the serializer as a [JSON
187+
API](http://jsonapi.org/) document.
188+
189+
### ActiveModelSerializers::SerializableResource
190+
191+
The **`ActiveModelSerializers::SerializableResource`** acts to coordinate the serializer(s) and adapter
192+
to an object that responds to `to_json`, and `as_json`. It is used in the controller to
193+
encapsulate the serialization resource when rendered. However, it can also be used on its own
194+
to serialize a resource outside of a controller, as well.
195+
196+
### Primitive handling
197+
198+
Definitions: A primitive is usually a String or Array. There is no serializer
199+
defined for them; they will be serialized when the resource is converted to JSON (`as_json` or
200+
`to_json`). (The below also applies for any object with no serializer.)
201+
202+
- ActiveModelSerializers doesn't handle primitives passed to `render json:` at all.
203+
204+
Internally, if no serializer can be found in the controller, the resource is not decorated by
205+
ActiveModelSerializers.
206+
207+
- However, when a primitive value is an attribute or in a collection, it is not modified.
208+
209+
When serializing a collection and the collection serializer (CollectionSerializer) cannot
210+
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).
211+
For example, when caught by `Reflection#build_association`, and the association value is set directly:
212+
213+
```ruby
214+
reflection_options[:virtual_value] = association_value.try(:as_json) || association_value
215+
```
216+
217+
(which is called by the adapter as `serializer.associations(*)`.)
218+
219+
### How options are parsed
220+
221+
High-level overview:
222+
223+
- For a **collection**
224+
- `:serializer` specifies the collection serializer and
225+
- `:each_serializer` specifies the serializer for each resource in the collection.
226+
- For a **single resource**, the `:serializer` option is the resource serializer.
227+
- Options are partitioned in serializer options and adapter options. Keys for adapter options are specified by
228+
[`ADAPTER_OPTION_KEYS`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/serializable_resource.rb#L5).
229+
The remaining options are serializer options.
230+
231+
Details:
232+
233+
1. **ActionController::Serialization**
234+
1. `serializable_resource = ActiveModelSerializers::SerializableResource.new(resource, options)`
235+
1. `options` are partitioned into `adapter_opts` and everything else (`serializer_opts`).
236+
The `adapter_opts` keys are defined in [`ActiveModelSerializers::SerializableResource::ADAPTER_OPTION_KEYS`](lib/active_model_serializers/serializable_resource.rb#L5).
237+
1. **ActiveModelSerializers::SerializableResource**
238+
1. `if serializable_resource.serializer?` (there is a serializer for the resource, and an adapter is used.)
239+
- Where `serializer?` is `use_adapter? && !!(serializer)`
240+
- Where `use_adapter?`: 'True when no explicit adapter given, or explicit value is truthy (non-nil);
241+
False when explicit adapter is falsy (nil or false)'
242+
- Where `serializer`:
243+
1. from explicit `:serializer` option, else
244+
2. implicitly from resource `ActiveModel::Serializer.serializer_for(resource)`
245+
1. A side-effect of checking `serializer` is:
246+
- The `:serializer` option is removed from the serializer_opts hash
247+
- If the `:each_serializer` option is present, it is removed from the serializer_opts hash and set as the `:serializer` option
248+
1. The serializer and adapter are created as
249+
1. `serializer_instance = serializer.new(resource, serializer_opts)`
250+
2. `adapter_instance = ActiveModel::Serializer::Adapter.create(serializer_instance, adapter_opts)`
251+
1. **ActiveModel::Serializer::CollectionSerializer#new**
252+
1. If the `serializer_instance` was a `CollectionSerializer` and the `:serializer` serializer_opts
253+
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).
254+
1. **ActiveModel::Serializer#attributes** is used by the adapter to get the attributes for
255+
resource as defined by the serializer.
256+
257+
(In Rails, the `options` are also passed to the `as_json(options)` or `to_json(options)`
258+
methods on the resource serialization by the Rails JSON renderer. They are, therefore, important
259+
to know about, but not part of ActiveModelSerializers.)
260+
261+
### What does a 'serializable resource' look like?
262+
263+
- An `ActiveRecord::Base` object.
264+
- Any Ruby object that passes the
265+
[Lint](http://www.rubydoc.info/github/rails-api/active_model_serializers/ActiveModel/Serializer/Lint/Tests)
266+
[code](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer/lint.rb).
267+
268+
ActiveModelSerializers provides a
269+
[`ActiveModelSerializers::Model`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/model.rb),
270+
which is a simple serializable PORO (Plain-Old Ruby Object).
271+
272+
`ActiveModelSerializers::Model` may be used either as a reference implementation, or in production code.
273+
274+
```ruby
275+
class MyModel < ActiveModelSerializers::Model
276+
attributes :id, :name, :level
277+
end
278+
```
279+
280+
The default serializer for `MyModel` would be `MyModelSerializer` whether MyModel is an
281+
ActiveRecord::Base object or not.
282+
283+
Outside of the controller the rules are **exactly** the same as for records. For example:
284+
285+
```ruby
286+
render json: MyModel.new(level: 'awesome'), adapter: :json
287+
```
288+
289+
would be serialized the same as
290+
291+
```ruby
292+
ActiveModelSerializers::SerializableResource.new(MyModel.new(level: 'awesome'), adapter: :json).as_json
293+
```
160294

161295
## Semantic Versioning
162296

Diff for: docs/ARCHITECTURE.md

-125
This file was deleted.

Diff for: docs/README.md

-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ This is the documentation of ActiveModelSerializers, it's focused on the **0.10.
1818
- JSON API
1919
- [Schema](jsonapi/schema.md)
2020
- [Errors](jsonapi/errors.md)
21-
- [ARCHITECTURE](ARCHITECTURE.md)
2221

2322
## How to
2423

Diff for: docs/general/rendering.md

+2-17
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,11 @@ render json: @posts, serializer: CollectionSerializer, each_serializer: PostPrev
4848

4949
## Serializing non-ActiveRecord objects
5050

51-
All serializable resources must pass the
52-
[ActiveModel::Serializer::Lint::Tests](../../lib/active_model/serializer/lint.rb#L17).
53-
54-
See the ActiveModelSerializers::Model for a base class that implements the full
55-
API for a plain-old Ruby object (PORO).
51+
See [README](../../README.md#what-does-a-serializable-resource-look-like)
5652

5753
## SerializableResource options
5854

59-
The `options` hash passed to `render` or `ActiveModelSerializers::SerializableResource.new(resource, options)`
60-
are partitioned into `serializer_opts` and `adapter_opts`. `adapter_opts` are passed to new Adapters;
61-
`serializer_opts` are passed to new Serializers.
62-
63-
The `adapter_opts` are specified in [ActiveModelSerializers::SerializableResource::ADAPTER_OPTIONS](../../lib/active_model_serializers/serializable_resource.rb#L5).
64-
The `serializer_opts` are the remaining options.
65-
66-
(In Rails, the `options` are also passed to the `as_json(options)` or `to_json(options)`
67-
methods on the resource serialization by the Rails JSON renderer. They are, therefore, important
68-
to know about, but not part of ActiveModelSerializers.)
69-
70-
See [ARCHITECTURE](../ARCHITECTURE.md) for more information.
55+
See [README](../../README.md#activemodelserializersserializableresource)
7156

7257
### adapter_opts
7358

0 commit comments

Comments
 (0)