active_model_serializers 0.8.3 → 0.10.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +702 -6
  3. data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
  4. data/README.md +194 -545
  5. data/lib/action_controller/serialization.rb +53 -38
  6. data/lib/active_model/serializable_resource.rb +13 -0
  7. data/lib/active_model/serializer/adapter/attributes.rb +17 -0
  8. data/lib/active_model/serializer/adapter/base.rb +20 -0
  9. data/lib/active_model/serializer/adapter/json.rb +17 -0
  10. data/lib/active_model/serializer/adapter/json_api.rb +17 -0
  11. data/lib/active_model/serializer/adapter/null.rb +17 -0
  12. data/lib/active_model/serializer/adapter.rb +26 -0
  13. data/lib/active_model/serializer/array_serializer.rb +14 -0
  14. data/lib/active_model/serializer/association.rb +73 -0
  15. data/lib/active_model/serializer/attribute.rb +27 -0
  16. data/lib/active_model/serializer/belongs_to_reflection.rb +13 -0
  17. data/lib/active_model/serializer/collection_serializer.rb +99 -0
  18. data/lib/active_model/serializer/concerns/caching.rb +305 -0
  19. data/lib/active_model/serializer/error_serializer.rb +16 -0
  20. data/lib/active_model/serializer/errors_serializer.rb +34 -0
  21. data/lib/active_model/serializer/field.rb +92 -0
  22. data/lib/active_model/serializer/fieldset.rb +33 -0
  23. data/lib/active_model/serializer/has_many_reflection.rb +12 -0
  24. data/lib/active_model/serializer/has_one_reflection.rb +9 -0
  25. data/lib/active_model/serializer/lazy_association.rb +99 -0
  26. data/lib/active_model/serializer/link.rb +23 -0
  27. data/lib/active_model/serializer/lint.rb +152 -0
  28. data/lib/active_model/serializer/null.rb +19 -0
  29. data/lib/active_model/serializer/reflection.rb +212 -0
  30. data/lib/active_model/serializer/version.rb +7 -0
  31. data/lib/active_model/serializer.rb +354 -442
  32. data/lib/active_model_serializers/adapter/attributes.rb +36 -0
  33. data/lib/active_model_serializers/adapter/base.rb +85 -0
  34. data/lib/active_model_serializers/adapter/json.rb +23 -0
  35. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +215 -0
  36. data/lib/active_model_serializers/adapter/json_api/error.rb +98 -0
  37. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +51 -0
  38. data/lib/active_model_serializers/adapter/json_api/link.rb +85 -0
  39. data/lib/active_model_serializers/adapter/json_api/meta.rb +39 -0
  40. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +94 -0
  41. data/lib/active_model_serializers/adapter/json_api/relationship.rb +106 -0
  42. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +68 -0
  43. data/lib/active_model_serializers/adapter/json_api.rb +535 -0
  44. data/lib/active_model_serializers/adapter/null.rb +11 -0
  45. data/lib/active_model_serializers/adapter.rb +100 -0
  46. data/lib/active_model_serializers/callbacks.rb +57 -0
  47. data/lib/active_model_serializers/deprecate.rb +56 -0
  48. data/lib/active_model_serializers/deserialization.rb +17 -0
  49. data/lib/active_model_serializers/json_pointer.rb +16 -0
  50. data/lib/active_model_serializers/logging.rb +124 -0
  51. data/lib/active_model_serializers/lookup_chain.rb +82 -0
  52. data/lib/active_model_serializers/model/caching.rb +26 -0
  53. data/lib/active_model_serializers/model.rb +132 -0
  54. data/lib/active_model_serializers/railtie.rb +52 -0
  55. data/lib/active_model_serializers/register_jsonapi_renderer.rb +80 -0
  56. data/lib/active_model_serializers/serializable_resource.rb +84 -0
  57. data/lib/active_model_serializers/serialization_context.rb +41 -0
  58. data/lib/active_model_serializers/test/schema.rb +140 -0
  59. data/lib/active_model_serializers/test/serializer.rb +127 -0
  60. data/lib/active_model_serializers/test.rb +9 -0
  61. data/lib/active_model_serializers.rb +49 -81
  62. data/lib/generators/rails/USAGE +6 -0
  63. data/lib/generators/rails/resource_override.rb +12 -0
  64. data/lib/generators/rails/serializer_generator.rb +38 -0
  65. data/lib/generators/rails/templates/serializer.rb.erb +8 -0
  66. data/lib/grape/active_model_serializers.rb +18 -0
  67. data/lib/grape/formatters/active_model_serializers.rb +34 -0
  68. data/lib/grape/helpers/active_model_serializers.rb +19 -0
  69. data/lib/tasks/rubocop.rake +55 -0
  70. metadata +265 -51
  71. data/.gitignore +0 -18
  72. data/.travis.yml +0 -28
  73. data/DESIGN.textile +0 -586
  74. data/Gemfile +0 -4
  75. data/Gemfile.edge +0 -9
  76. data/Rakefile +0 -18
  77. data/active_model_serializers.gemspec +0 -24
  78. data/bench/perf.rb +0 -43
  79. data/cruft.md +0 -19
  80. data/lib/active_model/array_serializer.rb +0 -104
  81. data/lib/active_model/serializer/associations.rb +0 -233
  82. data/lib/active_model/serializers/version.rb +0 -5
  83. data/lib/active_record/serializer_override.rb +0 -16
  84. data/lib/generators/resource_override.rb +0 -13
  85. data/lib/generators/serializer/USAGE +0 -9
  86. data/lib/generators/serializer/serializer_generator.rb +0 -42
  87. data/lib/generators/serializer/templates/serializer.rb +0 -19
  88. data/test/array_serializer_test.rb +0 -75
  89. data/test/association_test.rb +0 -592
  90. data/test/caching_test.rb +0 -96
  91. data/test/generators_test.rb +0 -85
  92. data/test/no_serialization_scope_test.rb +0 -34
  93. data/test/serialization_scope_name_test.rb +0 -67
  94. data/test/serialization_test.rb +0 -392
  95. data/test/serializer_support_test.rb +0 -51
  96. data/test/serializer_test.rb +0 -1465
  97. data/test/test_fakes.rb +0 -217
  98. data/test/test_helper.rb +0 -32
data/README.md CHANGED
@@ -1,655 +1,304 @@
1
- [![Build Status](https://api.travis-ci.org/rails-api/active_model_serializers.png)](https://travis-ci.org/rails-api/active_model_serializers) [![Code Climate](https://codeclimate.com/github/rails-api/active_model_serializers.png)](https://codeclimate.com/github/rails-api/active_model_serializers)
1
+ # ActiveModelSerializers
2
2
 
3
- # Purpose
3
+ <table>
4
+ <tr>
5
+ <td>Build Status</td>
6
+ <td>
7
+ <a href="https://travis-ci.org/rails-api/active_model_serializers"><img src="https://api.travis-ci.org/rails-api/active_model_serializers.svg?branch=0-10-stable" alt="Build Status" ></a>
8
+ <a href="https://ci.appveyor.com/project/bf4/active-model-serializers/branch/0-10-stable"><img src="https://ci.appveyor.com/api/projects/status/x6xdjydutm54gvyt/branch/master?svg=true" alt="Build status"></a>
9
+ </td>
10
+ </tr>
11
+ <tr>
12
+ <td>Code Quality</td>
13
+ <td>
14
+ <a href="https://codeclimate.com/github/rails-api/active_model_serializers"><img src="https://codeclimate.com/github/rails-api/active_model_serializers/badges/gpa.svg" alt="Code Quality"></a>
15
+ <a href="https://codebeat.co/projects/github-com-rails-api-active_model_serializers"><img src="https://codebeat.co/badges/a9ab35fa-8b5a-4680-9d4e-a81f9a55ebcd" alt="codebeat" ></a>
16
+ </td>
17
+ </tr>
18
+ <tr>
19
+ <td>Issue Stats</td>
20
+ <td>
21
+ <a href="https://github.com/rails-api/active_model_serializers/pulse/monthly">Pulse</a>
22
+ </td>
23
+ </tr>
24
+ </table>
4
25
 
5
- The purpose of `ActiveModel::Serializers` is to provide an object to
6
- encapsulate serialization of `ActiveModel` objects, including `ActiveRecord`
7
- objects.
26
+ ## About
8
27
 
9
- Serializers know about both a model and the `current_user`, so you can
10
- customize serialization based upon whether a user is authorized to see the
11
- content.
28
+ ActiveModelSerializers brings convention over configuration to your JSON generation.
12
29
 
13
- In short, **serializers replace hash-driven development with object-oriented
14
- development.**
30
+ ActiveModelSerializers works through two components: **serializers** and **adapters**.
15
31
 
16
- # Installing Serializers
32
+ Serializers describe _which_ attributes and relationships should be serialized.
17
33
 
18
- The easiest way to install `ActiveModel::Serializers` is to add it to your
19
- `Gemfile`:
34
+ Adapters describe _how_ attributes and relationships should be serialized.
20
35
 
21
- ```ruby
22
- gem "active_model_serializers", "~> 0.8.0"
23
- ```
36
+ SerializableResource co-ordinates the resource, Adapter and Serializer to produce the
37
+ resource serialization. The serialization has the `#as_json`, `#to_json` and `#serializable_hash`
38
+ methods used by the Rails JSON Renderer. (SerializableResource actually delegates
39
+ these methods to the adapter.)
24
40
 
25
- Then, install it on the command line:
41
+ By default ActiveModelSerializers will use the **Attributes Adapter** (no JSON root).
42
+ But we strongly advise you to use **JsonApi Adapter**, which
43
+ follows 1.0 of the format specified in [jsonapi.org/format](https://jsonapi.org/format).
44
+ Check how to change the adapter in the sections below.
26
45
 
27
- ```
28
- $ bundle install
29
- ```
46
+ `0.10.x` is **not** backward compatible with `0.9.x` nor `0.8.x`.
30
47
 
31
- # Creating a Serializer
48
+ `0.10.x` is based on the `0.8.0` code, but with a more flexible
49
+ architecture. We'd love your help. [Learn how you can help here.](CONTRIBUTING.md)
32
50
 
33
- The easiest way to create a new serializer is to generate a new resource, which
34
- will generate a serializer at the same time:
51
+ ## Installation
35
52
 
36
- ```
37
- $ rails g resource post title:string body:string
38
- ```
39
-
40
- This will generate a serializer in `app/serializers/post_serializer.rb` for
41
- your new model. You can also generate a serializer for an existing model with
42
- the serializer generator:
53
+ Add this line to your application's Gemfile:
43
54
 
44
55
  ```
45
- $ rails g serializer post
56
+ gem 'active_model_serializers', '~> 0.10.0'
46
57
  ```
47
58
 
48
- ### Support for PORO's and other ORM's.
49
-
50
- Currently `ActiveModel::Serializers` adds serialization support to all models
51
- that descend from `ActiveRecord` or include `Mongoid::Document`. If you are
52
- using another ORM, or if you are using objects that are `ActiveModel`
53
- compliant but do not descend from `ActiveRecord` or include
54
- `Mongoid::Document`, you must add an include statement for
55
- `ActiveModel::SerializerSupport` to make models serializable. If you
56
- also want to make collections serializable, you should include
57
- `ActiveModel::ArraySerializerSupport` into your ORM's
58
- relation/criteria class.
59
-
60
- # ActiveModel::Serializer
59
+ And then execute:
61
60
 
62
- All new serializers descend from ActiveModel::Serializer
63
-
64
- # render :json
65
-
66
- In your controllers, when you use `render :json`, Rails will now first search
67
- for a serializer for the object and use it if available.
68
-
69
- ```ruby
70
- class PostsController < ApplicationController
71
- def show
72
- @post = Post.find(params[:id])
73
- render :json => @post
74
- end
75
- end
76
61
  ```
77
-
78
- In this case, Rails will look for a serializer named `PostSerializer`, and if
79
- it exists, use it to serialize the `Post`.
80
-
81
- This also works with `respond_with`, which uses `to_json` under the hood. Also
82
- note that any options passed to `render :json` will be passed to your
83
- serializer and available as `@options` inside.
84
-
85
- To specify a custom serializer for an object, there are 2 options:
86
-
87
- #### 1. Specify the serializer in your model:
88
-
89
- ```ruby
90
- class Post < ActiveRecord::Base
91
- def active_model_serializer
92
- FancyPostSerializer
93
- end
94
- end
62
+ $ bundle
95
63
  ```
96
64
 
97
- #### 2. Specify the serializer when you render the object:
65
+ ## Getting Started
98
66
 
99
- ```ruby
100
- render :json => @post, :serializer => FancyPostSerializer
101
- ```
67
+ See [Getting Started](docs/general/getting_started.md) for the nuts and bolts.
102
68
 
103
- ## Arrays
69
+ More information is available in the [Guides](docs) and
70
+ [High-level behavior](README.md#high-level-behavior).
104
71
 
105
- In your controllers, when you use `render :json` for an array of objects, AMS will
106
- use `ActiveModel::ArraySerializer` (included in this project) as the base serializer,
107
- and the individual `Serializer` for the objects contained in that array.
72
+ ## Getting Help
108
73
 
109
- ```ruby
110
- class PostSerializer < ActiveModel::Serializer
111
- attributes :title, :body
112
- end
74
+ If you find a bug, please report an [Issue](https://github.com/rails-api/active_model_serializers/issues/new)
75
+ and see our [contributing guide](CONTRIBUTING.md).
113
76
 
114
- class PostsController < ApplicationController
115
- def index
116
- @posts = Post.all
117
- render :json => @posts
118
- end
119
- end
120
- ```
121
-
122
- Given the example above, the index action will return
77
+ If you have a question, please [post to Stack Overflow](https://stackoverflow.com/questions/tagged/active-model-serializers).
123
78
 
124
- ```json
125
- {
126
- "posts":
127
- [
128
- { "title": "Post 1", "body": "Hello!" },
129
- { "title": "Post 2", "body": "Goodbye!" }
130
- ]
131
- }
132
- ```
79
+ If you'd like to chat, we have a [community slack](https://amserializers.herokuapp.com).
133
80
 
134
- By default, the root element is the name of the controller. For example, `PostsController`
135
- generates a root element "posts". To change it:
81
+ Thanks!
136
82
 
137
- ```ruby
138
- render :json => @posts, :root => "some_posts"
139
- ```
83
+ ## Documentation
140
84
 
141
- You may disable the root element for arrays at the top level, which will result in
142
- more concise json. See the next section for ways on how to do this. Disabling the
143
- root element of the array with any of those methods will produce
85
+ If you're reading this at https://github.com/rails-api/active_model_serializers you are
86
+ reading documentation for our `master`, which may include features that have not
87
+ been released yet. Please see below for the documentation relevant to you.
144
88
 
145
- ```json
146
- [
147
- { "title": "Post 1", "body": "Hello!" },
148
- { "title": "Post 2", "body": "Goodbye!" }
149
- ]
150
- ```
89
+ - [0.10 (0-10-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-10-stable)
90
+ - [0.10.10 (latest release) Documentation](https://github.com/rails-api/active_model_serializers/tree/v0.10.10)
91
+ - [![API Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/gems/active_model_serializers/0.10.10)
92
+ - [Guides](docs)
93
+ - [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable)
94
+ - [![API Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/gems/active_model_serializers/0.9.7)
95
+ - [0.8 (0-8-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-8-stable)
96
+ - [![API Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/gems/active_model_serializers/0.8.4)
151
97
 
152
- To specify a custom serializer for the items within an array:
153
98
 
154
- ```ruby
155
- render :json => @posts, :each_serializer => FancyPostSerializer
156
- ```
99
+ ## High-level behavior
157
100
 
158
- ## Disabling the root element
101
+ Choose an adapter from [adapters](lib/active_model_serializers/adapter):
159
102
 
160
- You have 4 options to disable the root element, each with a slightly different scope:
161
-
162
- #### 1. Disable root globally for all, or per class
163
-
164
- In an initializer:
165
-
166
- ```ruby
167
- ActiveSupport.on_load(:active_model_serializers) do
168
- # Disable for all serializers (except ArraySerializer)
169
- ActiveModel::Serializer.root = false
170
-
171
- # Disable for ArraySerializer
172
- ActiveModel::ArraySerializer.root = false
173
- end
174
- ```
175
-
176
- #### 2. Disable root per render call in your controller
177
-
178
- ```ruby
179
- render :json => @posts, :root => false
180
- ```
181
-
182
- #### 3. Subclass the serializer, and specify using it
183
-
184
- ```ruby
185
- class CustomArraySerializer < ActiveModel::ArraySerializer
186
- self.root = false
187
- end
188
-
189
- # controller:
190
- render :json => @posts, :serializer => CustomArraySerializer
191
- ```
192
-
193
- #### 4. Define default_serializer_options in your controller
194
-
195
- If you define `default_serializer_options` method in your controller,
196
- all serializers in actions of this controller and it's children will use them.
197
- One of the options may be `root: false`
198
-
199
- ```ruby
200
- def default_serializer_options
201
- {
202
- root: false
203
- }
204
- end
103
+ ``` ruby
104
+ ActiveModelSerializers.config.adapter = :json_api # Default: `:attributes`
205
105
  ```
206
106
 
207
- ## Getting the old version
208
-
209
- If you find that your project is already relying on the old rails to_json
210
- change `render :json` to `render :json => @your_object.to_json`.
211
-
212
- # Attributes and Associations
213
-
214
- Once you have a serializer, you can specify which attributes and associations
215
- you would like to include in the serialized form.
107
+ Given a [serializable model](lib/active_model/serializer/lint.rb):
216
108
 
217
109
  ```ruby
218
- class PostSerializer < ActiveModel::Serializer
219
- attributes :id, :title, :body
220
- has_many :comments
110
+ # either
111
+ class SomeResource < ActiveRecord::Base
112
+ # columns: title, body
221
113
  end
222
- ```
223
-
224
- ## Attributes
225
-
226
- For specified attributes, a serializer will look up the attribute on the
227
- object you passed to `render :json`. It uses
228
- `read_attribute_for_serialization`, which `ActiveRecord` objects implement as a
229
- regular attribute lookup.
230
-
231
- Before looking up the attribute on the object, a serializer will check for the
232
- presence of a method with the name of the attribute. This allows serializers to
233
- include properties beyond the simple attributes of the model. For example:
234
-
235
- ```ruby
236
- class PersonSerializer < ActiveModel::Serializer
237
- attributes :first_name, :last_name, :full_name
238
-
239
- def full_name
240
- "#{object.first_name} #{object.last_name}"
241
- end
242
- end
243
- ```
244
-
245
- Within a serializer's methods, you can access the object being
246
- serialized as `object`.
247
-
248
- You can also access the `current_user` method, which provides an
249
- authorization context to your serializer. By default, the context
250
- is the current user of your application, but this
251
- [can be customized](#customizing-scope).
252
-
253
- Serializers will check for the presence of a method named
254
- `include_[ATTRIBUTE]?` to determine whether a particular attribute should be
255
- included in the output. This is typically used to customize output
256
- based on `current_user`. For example:
257
-
258
- ```ruby
259
- class PostSerializer < ActiveModel::Serializer
260
- attributes :id, :title, :body, :author
261
-
262
- def include_author?
263
- current_user.admin?
264
- end
114
+ # or
115
+ class SomeResource < ActiveModelSerializers::Model
116
+ attributes :title, :body
265
117
  end
266
118
  ```
267
119
 
268
- The type of a computed attribute (like :full_name above) is not easily
269
- calculated without some sophisticated static code analysis. To specify the
270
- type of a computed attribute:
120
+ And initialized as:
271
121
 
272
122
  ```ruby
273
- class PersonSerializer < ActiveModel::Serializer
274
- attributes :first_name, :last_name, {:full_name => :string}
275
-
276
- def full_name
277
- "#{object.first_name} #{object.last_name}"
278
- end
279
- end
123
+ resource = SomeResource.new(title: 'ActiveModelSerializers', body: 'Convention over configuration')
280
124
  ```
281
125
 
282
- If you would like the key in the outputted JSON to be different from its name
283
- in ActiveRecord, you can use the `:key` option to customize it:
126
+ Given a serializer for the serializable model:
284
127
 
285
128
  ```ruby
286
- class PostSerializer < ActiveModel::Serializer
287
- attributes :id, :body
288
-
289
- # look up :subject on the model, but use +title+ in the JSON
290
- attribute :subject, :key => :title
291
- has_many :comments
129
+ class SomeSerializer < ActiveModel::Serializer
130
+ attribute :title, key: :name
131
+ attributes :body
292
132
  end
293
133
  ```
294
134
 
295
- If you would like to add meta information to the outputted JSON, use the `:meta`
296
- option:
297
-
298
- ```ruby
299
- render :json => @posts, :serializer => CustomArraySerializer, :meta => {:total => 10}
300
- ```
301
-
302
- The above usage of `:meta` will produce the following:
303
-
304
- ```json
305
- {
306
- "meta": { "total": 10 },
307
- "posts": [
308
- { "title": "Post 1", "body": "Hello!" },
309
- { "title": "Post 2", "body": "Goodbye!" }
310
- ]
311
- }
312
- ```
313
-
314
- If you would like to change the meta key name you can use the `:meta_key` option:
315
-
316
- ```ruby
317
- render :json => @posts, :serializer => CustomArraySerializer, :meta => {:total => 10}, :meta_key => 'meta_object'
318
- ```
319
-
320
- The above usage of `:meta_key` will produce the following:
321
-
322
- ```json
323
- {
324
- "meta_object": { "total": 10 },
325
- "posts": [
326
- { "title": "Post 1", "body": "Hello!" },
327
- { "title": "Post 2", "body": "Goodbye!" }
328
- ]
329
- }
330
- ```
331
-
332
- If you would like direct, low-level control of attribute serialization, you can
333
- completely override the `attributes` method to return the hash you need:
135
+ The model can be serialized as:
334
136
 
335
137
  ```ruby
336
- class PersonSerializer < ActiveModel::Serializer
337
- attributes :first_name, :last_name
338
-
339
- def attributes
340
- hash = super
341
- if current_user.admin?
342
- hash["ssn"] = object.ssn
343
- hash["secret"] = object.mothers_maiden_name
344
- end
345
- hash
346
- end
347
- end
138
+ options = {}
139
+ serialization = ActiveModelSerializers::SerializableResource.new(resource, options)
140
+ serialization.to_json
141
+ serialization.as_json
348
142
  ```
349
143
 
350
- ## Associations
351
-
352
- For specified associations, the serializer will look up the association and
353
- then serialize each element of the association. For instance, a `has_many
354
- :comments` association will create a new `CommentSerializer` for each comment
355
- and use it to serialize the comment.
356
-
357
- By default, serializers simply look up the association on the original object.
358
- You can customize this behavior by implementing a method with the name of the
359
- association and returning a different Array. Often, you will do this to
360
- customize the objects returned based on the current user.
144
+ SerializableResource delegates to the adapter, which it builds as:
361
145
 
362
146
  ```ruby
363
- class PostSerializer < ActiveModel::Serializer
364
- attributes :id, :title, :body
365
- has_many :comments
366
-
367
- # only let the user see comments he created.
368
- def comments
369
- object.comments.where(:created_by => current_user)
370
- end
371
- end
147
+ adapter_options = {}
148
+ adapter = ActiveModelSerializers::Adapter.create(serializer, adapter_options)
149
+ adapter.to_json
150
+ adapter.as_json
151
+ adapter.serializable_hash
372
152
  ```
373
153
 
374
- As with attributes, you can change the JSON key that the serializer should
375
- use for a particular association.
154
+ The adapter formats the serializer's attributes and associations (a.k.a. includes):
376
155
 
377
156
  ```ruby
378
- class PostSerializer < ActiveModel::Serializer
379
- attributes :id, :title, :body
380
-
381
- # look up comments, but use +my_comments+ as the key in JSON
382
- has_many :comments, :key => :my_comments
383
- end
157
+ serializer_options = {}
158
+ serializer = SomeSerializer.new(resource, serializer_options)
159
+ serializer.attributes
160
+ serializer.associations
384
161
  ```
385
162
 
386
- Also, as with attributes, serializers will check for the presence
387
- of a method named `include_[ASSOCIATION]?` to determine whether a particular association
388
- should be included in the output. For example:
163
+ ## Architecture
389
164
 
390
- ```ruby
391
- class PostSerializer < ActiveModel::Serializer
392
- attributes :id, :title, :body
393
- has_many :comments
165
+ 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,
166
+ please refer to the [0.8 README](https://github.com/rails-api/active_model_serializers/blob/0-8-stable/README.md) or
167
+ [0.9 README](https://github.com/rails-api/active_model_serializers/blob/0-9-stable/README.md).
394
168
 
395
- def include_comments?
396
- !object.comments_disabled?
397
- end
398
- end
399
- ```
169
+ The original design is also available [here](https://github.com/rails-api/active_model_serializers/blob/d72b66d4c5355b0ff0a75a04895fcc4ea5b0c65e/README.textile).
400
170
 
401
- If you would like lower-level control of association serialization, you can
402
- override `include_associations!` to specify which associations should be included:
171
+ ### ActiveModel::Serializer
403
172
 
404
- ```ruby
405
- class PostSerializer < ActiveModel::Serializer
406
- attributes :id, :title, :body
407
- has_one :author
408
- has_many :comments
409
-
410
- def include_associations!
411
- include! :author if current_user.admin?
412
- include! :comments unless object.comments_disabled?
413
- end
414
- end
415
- ```
173
+ An **`ActiveModel::Serializer`** wraps a [serializable resource](https://github.com/rails/rails/blob/master/activemodel/lib/active_model/serialization.rb)
174
+ and exposes an `attributes` method, among a few others.
175
+ It allows you to specify which attributes and associations should be represented in the serializatation of the resource.
176
+ It requires an adapter to transform its attributes into a JSON document; it cannot be serialized itself.
177
+ It may be useful to think of it as a
178
+ [presenter](https://blog.steveklabnik.com/posts/2011-09-09-better-ruby-presenters).
416
179
 
417
- You may also use the `:serializer` option to specify a custom serializer class and the `:polymorphic` option to specify an association that is polymorphic (STI), e.g.:
180
+ #### ActiveModel::CollectionSerializer
418
181
 
419
- ```ruby
420
- has_many :comments, :serializer => CommentShortSerializer
421
- has_one :reviewer, :polymorphic => true
422
- ```
182
+ The **`ActiveModel::CollectionSerializer`** represents a collection of resources as serializers
183
+ and, if there is no serializer, primitives.
423
184
 
424
- Serializers are only concerned with multiplicity, and not ownership. `belongs_to` ActiveRecord associations can be included using `has_one` in your serializer.
185
+ ### ActiveModelSerializers::Adapter::Base
425
186
 
426
- ## Embedding Associations
187
+ The **`ActiveModelSerializers::Adapter::Base`** describes the structure of the JSON document generated from a
188
+ serializer. For example, the `Attributes` example represents each serializer as its
189
+ unmodified attributes. The `JsonApi` adapter represents the serializer as a [JSON
190
+ API](https://jsonapi.org/) document.
427
191
 
428
- By default, associations will be embedded inside the serialized object. So if
429
- you have a post, the outputted JSON will look like:
192
+ ### ActiveModelSerializers::SerializableResource
430
193
 
431
- ```json
432
- {
433
- "post": {
434
- "id": 1,
435
- "title": "New post",
436
- "body": "A body!",
437
- "comments": [
438
- { "id": 1, "body": "what a dumb post" }
439
- ]
440
- }
441
- }
442
- ```
194
+ The **`ActiveModelSerializers::SerializableResource`** acts to coordinate the serializer(s) and adapter
195
+ to an object that responds to `to_json`, and `as_json`. It is used in the controller to
196
+ encapsulate the serialization resource when rendered. However, it can also be used on its own
197
+ to serialize a resource outside of a controller, as well.
443
198
 
444
- This is convenient for simple use-cases, but for more complex clients, it is
445
- better to supply an Array of IDs for the association. This makes your API more
446
- flexible from a performance standpoint and avoids wasteful duplication.
199
+ ### Primitive handling
447
200
 
448
- To embed IDs instead of associations, simply use the `embed` class method:
201
+ Definitions: A primitive is usually a String or Array. There is no serializer
202
+ defined for them; they will be serialized when the resource is converted to JSON (`as_json` or
203
+ `to_json`). (The below also applies for any object with no serializer.)
449
204
 
450
- ```ruby
451
- class PostSerializer < ActiveModel::Serializer
452
- embed :ids
205
+ - ActiveModelSerializers doesn't handle primitives passed to `render json:` at all.
453
206
 
454
- attributes :id, :title, :body
455
- has_many :comments
456
- end
457
- ```
207
+ Internally, if no serializer can be found in the controller, the resource is not decorated by
208
+ ActiveModelSerializers.
458
209
 
459
- Now, any associations will be supplied as an Array of IDs:
460
-
461
- ```json
462
- {
463
- "post": {
464
- "id": 1,
465
- "title": "New post",
466
- "body": "A body!",
467
- "comment_ids": [ 1, 2, 3 ]
468
- }
469
- }
470
- ```
210
+ - However, when a primitive value is an attribute or in a collection, it is not modified.
471
211
 
472
- Alternatively, you can choose to embed only the ids or the associated objects per association:
212
+ When serializing a collection and the collection serializer (CollectionSerializer) cannot
213
+ 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).
214
+ For example, when caught by `Reflection#build_association`, and the association value is set directly:
473
215
 
474
216
  ```ruby
475
- class PostSerializer < ActiveModel::Serializer
476
- attributes :id, :title, :body
477
-
478
- has_many :comments, embed: :objects
479
- has_many :tags, embed: :ids
480
- end
217
+ reflection_options[:virtual_value] = association_value.try(:as_json) || association_value
481
218
  ```
482
219
 
483
- The JSON will look like this:
484
-
485
- ```json
486
- {
487
- "post": {
488
- "id": 1,
489
- "title": "New post",
490
- "body": "A body!",
491
- "comments": [
492
- { "id": 1, "body": "what a dumb post" }
493
- ],
494
- "tag_ids": [ 1, 2, 3 ]
495
- }
496
- }
497
- ```
220
+ (which is called by the adapter as `serializer.associations(*)`.)
498
221
 
499
- In addition to supplying an Array of IDs, you may want to side-load the data
500
- alongside the main object. This makes it easier to process the entire package
501
- of data without having to recursively scan the tree looking for embedded
502
- information. It also ensures that associations that are shared between several
503
- objects (like tags), are only delivered once for the entire payload.
222
+ ### How options are parsed
504
223
 
505
- You can specify that the data be included like this:
224
+ High-level overview:
506
225
 
507
- ```ruby
508
- class PostSerializer < ActiveModel::Serializer
509
- embed :ids, :include => true
226
+ - For a **collection**
227
+ - `:serializer` specifies the collection serializer and
228
+ - `:each_serializer` specifies the serializer for each resource in the collection.
229
+ - For a **single resource**, the `:serializer` option is the resource serializer.
230
+ - Options are partitioned in serializer options and adapter options. Keys for adapter options are specified by
231
+ [`ADAPTER_OPTION_KEYS`](lib/active_model_serializers/serializable_resource.rb#L5).
232
+ The remaining options are serializer options.
510
233
 
511
- attributes :id, :title, :body
512
- has_many :comments
513
- end
514
- ```
234
+ Details:
515
235
 
516
- Assuming that the comments also `has_many :tags`, you will get a JSON like
517
- this:
518
-
519
- ```json
520
- {
521
- "post": {
522
- "id": 1,
523
- "title": "New post",
524
- "body": "A body!",
525
- "comment_ids": [ 1, 2 ]
526
- },
527
- "comments": [
528
- { "id": 1, "body": "what a dumb post", "tag_ids": [ 1, 2 ] },
529
- { "id": 2, "body": "i liked it", "tag_ids": [ 1, 3 ] },
530
- ],
531
- "tags": [
532
- { "id": 1, "name": "short" },
533
- { "id": 2, "name": "whiny" },
534
- { "id": 3, "name": "happy" }
535
- ]
536
- }
537
- ```
236
+ 1. **ActionController::Serialization**
237
+ 1. `serializable_resource = ActiveModelSerializers::SerializableResource.new(resource, options)`
238
+ 1. `options` are partitioned into `adapter_opts` and everything else (`serializer_opts`).
239
+ The `adapter_opts` keys are defined in [`ActiveModelSerializers::SerializableResource::ADAPTER_OPTION_KEYS`](lib/active_model_serializers/serializable_resource.rb#L5).
240
+ 1. **ActiveModelSerializers::SerializableResource**
241
+ 1. `if serializable_resource.serializer?` (there is a serializer for the resource, and an adapter is used.)
242
+ - Where `serializer?` is `use_adapter? && !!(serializer)`
243
+ - Where `use_adapter?`: 'True when no explicit adapter given, or explicit value is truthy (non-nil);
244
+ False when explicit adapter is falsy (nil or false)'
245
+ - Where `serializer`:
246
+ 1. from explicit `:serializer` option, else
247
+ 2. implicitly from resource `ActiveModel::Serializer.serializer_for(resource)`
248
+ 1. A side-effect of checking `serializer` is:
249
+ - The `:serializer` option is removed from the serializer_opts hash
250
+ - If the `:each_serializer` option is present, it is removed from the serializer_opts hash and set as the `:serializer` option
251
+ 1. The serializer and adapter are created as
252
+ 1. `serializer_instance = serializer.new(resource, serializer_opts)`
253
+ 2. `adapter_instance = ActiveModel::Serializer::Adapter.create(serializer_instance, adapter_opts)`
254
+ 1. **ActiveModel::Serializer::CollectionSerializer#new**
255
+ 1. If the `serializer_instance` was a `CollectionSerializer` and the `:serializer` serializer_opts
256
+ is present, then [that serializer is passed into each resource](https://github.com/rails-api/active_model_serializers/blob/0-10-stable/lib/active_model/serializer/collection_serializer.rb#L77-L79).
257
+ 1. **ActiveModel::Serializer#attributes** is used by the adapter to get the attributes for
258
+ resource as defined by the serializer.
538
259
 
539
- You can also specify a different root for the embedded objects than the key
540
- used to reference them:
260
+ (In Rails, the `options` are also passed to the `as_json(options)` or `to_json(options)`
261
+ methods on the resource serialization by the Rails JSON renderer. They are, therefore, important
262
+ to know about, but not part of ActiveModelSerializers.)
541
263
 
542
- ```ruby
543
- class PostSerializer < ActiveModel::Serializer
544
- embed :ids, :include => true
264
+ ### What does a 'serializable resource' look like?
545
265
 
546
- attributes :id, :title, :body
547
- has_many :comments, :key => :comment_ids, :root => :comment_objects
548
- end
549
- ```
266
+ - An `ActiveRecord::Base` object.
267
+ - Any Ruby object that passes the
268
+ [Lint](https://www.rubydoc.info/gems/active_model_serializers/ActiveModel/Serializer/Lint/Tests)
269
+ [(code)](lib/active_model/serializer/lint.rb).
550
270
 
551
- This would generate JSON that would look like this:
552
-
553
- ```json
554
- {
555
- "post": {
556
- "id": 1,
557
- "title": "New post",
558
- "body": "A body!",
559
- "comment_ids": [ 1 ]
560
- },
561
- "comment_objects": [
562
- { "id": 1, "body": "what a dumb post" }
563
- ]
564
- }
565
- ```
271
+ ActiveModelSerializers provides a
272
+ [`ActiveModelSerializers::Model`](lib/active_model_serializers/model.rb),
273
+ which is a simple serializable PORO (Plain-Old Ruby Object).
566
274
 
567
- You can also specify a different attribute to use rather than the ID of the
568
- objects:
275
+ `ActiveModelSerializers::Model` may be used either as a reference implementation, or in production code.
569
276
 
570
277
  ```ruby
571
- class PostSerializer < ActiveModel::Serializer
572
- embed :ids, :include => true
573
-
574
- attributes :id, :title, :body
575
- has_many :comments, :embed_key => :external_id
278
+ class MyModel < ActiveModelSerializers::Model
279
+ attributes :id, :name, :level
576
280
  end
577
281
  ```
578
282
 
579
- This would generate JSON that would look like this:
580
-
581
- ```json
582
- {
583
- "post": {
584
- "id": 1,
585
- "title": "New post",
586
- "body": "A body!",
587
- "comment_ids": [ "COMM001" ]
588
- },
589
- "comments": [
590
- { "id": 1, "external_id": "COMM001", "body": "what a dumb post" }
591
- ]
592
- }
593
- ```
594
-
595
- **NOTE**: The `embed :ids` mechanism is primary useful for clients that process
596
- data in bulk and load it into a local store. For these clients, the ability to
597
- easily see all of the data per type, rather than having to recursively scan the
598
- data looking for information, is extremely useful.
283
+ The default serializer for `MyModel` would be `MyModelSerializer` whether MyModel is an
284
+ ActiveRecord::Base object or not.
599
285
 
600
- If you are mostly working with the data in simple scenarios and manually making
601
- Ajax requests, you probably just want to use the default embedded behavior.
602
-
603
- ## Customizing Scope
604
-
605
- In a serializer, `current_user` is the current authorization scope which the controller
606
- provides to the serializer when you call `render :json`. By default, this is
607
- `current_user`, but can be customized in your controller by calling
608
- `serialization_scope`:
286
+ Outside of the controller the rules are **exactly** the same as for records. For example:
609
287
 
610
288
  ```ruby
611
- class ApplicationController < ActionController::Base
612
- serialization_scope :current_admin
613
- end
289
+ render json: MyModel.new(level: 'awesome'), adapter: :json
614
290
  ```
615
291
 
616
- The above example will also change the scope name from `current_user` to
617
- `current_admin`.
618
-
619
- Please note that, until now, `serialization_scope` doesn't accept a second
620
- object with options for specifying which actions should or should not take a
621
- given scope in consideration.
622
-
623
- To be clear, it's not possible, yet, to do something like this:
292
+ would be serialized the same as
624
293
 
625
294
  ```ruby
626
- class SomeController < ApplicationController
627
- serialization_scope :current_admin, :except => [:index, :show]
628
- end
295
+ ActiveModelSerializers::SerializableResource.new(MyModel.new(level: 'awesome'), adapter: :json).as_json
629
296
  ```
630
297
 
631
- So, in order to have a fine grained control of what each action should take in
632
- consideration for its scope, you may use something like this:
633
-
634
- ```ruby
635
- class CitiesController < ApplicationController
636
- serialization_scope nil
637
-
638
- def index
639
- @cities = City.all
640
-
641
- render :json => @cities, :each_serializer => CitySerializer
642
- end
298
+ ## Semantic Versioning
643
299
 
644
- def show
645
- @city = City.find(params[:id])
300
+ This project adheres to [semver](https://semver.org/)
646
301
 
647
- render :json => @city, :scope => current_admin, :scope_name => :current_admin
648
- end
649
- end
650
- ```
302
+ ## Contributing
651
303
 
652
- Assuming that the `current_admin` method needs to make a query in the database
653
- for the current user, the advantage of this approach is that, by setting
654
- `serialization_scope` to `nil`, the `index` action no longer will need to make
655
- that query, only the `show` action will.
304
+ See [CONTRIBUTING.md](CONTRIBUTING.md)