@@ -11,8 +11,6 @@ This guide describes how to use Active Model serializers to build non-trivial JS
11
11
This guide covers an intermediate topic and assumes familiarity with Rails conventions. It is suitable for applications that expose a
12
12
JSON API that may return different results based on the authorization status of the user.
13
13
14
- endprologue.
15
-
16
14
h3. Serialization
17
15
18
16
By default, Active Record objects can serialize themselves into JSON by using the `to_json` method. This method takes a series of additional
@@ -37,7 +35,7 @@ h3. The Most Basic Serializer
37
35
38
36
A basic serializer is a simple Ruby object named after the model class it is serializing.
39
37
40
- <code><pre >
38
+ <pre><code >
41
39
class PostSerializer
42
40
def initialize(post, scope)
43
41
@post, @scope = post, scope
@@ -47,22 +45,22 @@ class PostSerializer
47
45
{ post: { title: @post.name, body: @post.body } }
48
46
end
49
47
end
50
- </pre ></code >
48
+ </code ></pre >
51
49
52
50
A serializer is initialized with two parameters: the model object it should serialize and an authorization scope. By default, the
53
51
authorization scope is the current user (+current_user+) but you can use a different object if you want. The serializer also
54
52
implements an +as_json+ method, which returns a Hash that will be sent to the JSON encoder.
55
53
56
54
Rails will transparently use your serializer when you use +render :json+ in your controller.
57
55
58
- <code><pre >
56
+ <pre><code >
59
57
class PostsController < ApplicationController
60
58
def show
61
59
@post = Post.find(params[:id])
62
60
render json: @post
63
61
end
64
62
end
65
- </pre ></code >
63
+ </code ></pre >
66
64
67
65
Because +respond_with+ uses +render :json+ under the hood for JSON requests, Rails will automatically use your serializer when
68
66
you use +respond_with+ as well.
@@ -72,7 +70,7 @@ h4. +serializable_hash+
72
70
In general, you will want to implement +serializable_hash+ and +as_json+ to allow serializers to embed associated content
73
71
directly. The easiest way to implement these two methods is to have +as_json+ call +serializable_hash+ and insert the root.
74
72
75
- <code><pre >
73
+ <pre><code >
76
74
class PostSerializer
77
75
def initialize(post, scope)
78
76
@post, @scope = post, scope
@@ -86,14 +84,14 @@ class PostSerializer
86
84
{ post: serializable_hash }
87
85
end
88
86
end
89
- </pre ></code >
87
+ </code ></pre >
90
88
91
89
h4. Authorization
92
90
93
91
Let's update our serializer to include the email address of the author of the post, but only if the current user has superuser
94
92
access.
95
93
96
- <code><pre >
94
+ <pre><code >
97
95
class PostSerializer
98
96
def initialize(post, scope)
99
97
@post, @scope = post, scope
@@ -122,14 +120,14 @@ private
122
120
@scope.superuser?
123
121
end
124
122
end
125
- </pre ></code >
123
+ </code ></pre >
126
124
127
125
h4. Testing
128
126
129
127
One benefit of encapsulating our objects this way is that it becomes extremely straight-forward to test the serialization
130
128
logic in isolation.
131
129
132
- <code><pre >
130
+ <pre><code >
133
131
require "ostruct"
134
132
135
133
class PostSerializerTest < ActiveSupport::TestCase
@@ -159,7 +157,7 @@ class PostSerializerTest < ActiveSupport::TestCase
159
157
assert_empty hash
160
158
end
161
159
end
162
- </pre ></code >
160
+ </code ></pre >
163
161
164
162
It's important to note that serializer objects define a clear interface specifically for serializing an existing object.
165
163
In this case, the serializer expects to receive a post object with +name+, +body+ and +email+ attributes and an authorization
@@ -170,7 +168,7 @@ the serializer doesn't need to concern itself with how the authorization scope d
170
168
whether it is set. In general, you should document these requirements in your serializer files and programatically via tests.
171
169
The documentation library +YARD+ provides excellent tools for describing this kind of requirement:
172
170
173
- <code><pre >
171
+ <pre><code >
174
172
class PostSerializer
175
173
# @param [~body, ~title, ~email] post the post to serialize
176
174
# @param [~super] scope the authorization scope for this serializer
@@ -180,7 +178,7 @@ class PostSerializer
180
178
181
179
# ...
182
180
end
183
- </pre ></code >
181
+ </code ></pre >
184
182
185
183
h3. Attribute Sugar
186
184
@@ -191,7 +189,7 @@ For example, you will sometimes want to simply include a number of existing attr
191
189
JSON. In the above example, the +title+ and +body+ attributes were always included in the JSON. Let's see how to use
192
190
+ActiveModel::Serializer+ to simplify our post serializer.
193
191
194
- <code><pre >
192
+ <pre><code >
195
193
class PostSerializer < ActiveModel::Serializer
196
194
attributes :title, :body
197
195
@@ -214,7 +212,7 @@ private
214
212
@scope.superuser?
215
213
end
216
214
end
217
- </pre ></code >
215
+ </code ></pre >
218
216
219
217
First, we specified the list of included attributes at the top of the class. This will create an instance method called
220
218
+attributes+ that extracts those attributes from the post model.
@@ -225,7 +223,7 @@ Next, we use the attributes methood in our +serializable_hash+ method, which all
225
223
earlier. We could also eliminate the +as_json+ method, as +ActiveModel::Serializer+ provides a default +as_json+ method for
226
224
us that calls our +serializable_hash+ method and inserts a root. But we can go a step further!
227
225
228
- <code><pre >
226
+ <pre><code >
229
227
class PostSerializer < ActiveModel::Serializer
230
228
attributes :title, :body
231
229
@@ -240,7 +238,7 @@ private
240
238
@scope.superuser?
241
239
end
242
240
end
243
- </pre ></code >
241
+ </code ></pre >
244
242
245
243
The superclass provides a default +initialize+ method as well as a default +serializable_hash+ method, which uses
246
244
+attributes+. We can call +super+ to get the hash based on the attributes we declared, and then add in any additional
@@ -253,7 +251,7 @@ h3. Associations
253
251
In most JSON APIs, you will want to include associated objects with your serialized object. In this case, let's include
254
252
the comments with the current post.
255
253
256
- <code><pre >
254
+ <pre><code >
257
255
class PostSerializer < ActiveModel::Serializer
258
256
attributes :title, :body
259
257
has_many :comments
@@ -269,11 +267,11 @@ private
269
267
@scope.superuser?
270
268
end
271
269
end
272
- </pre ></code >
270
+ </code ></pre >
273
271
274
272
The default +serializable_hash+ method will include the comments as embedded objects inside the post.
275
273
276
- <code><pre >
274
+ <pre><code >
277
275
{
278
276
post: {
279
277
title: "Hello Blog!",
@@ -286,14 +284,14 @@ The default +serializable_hash+ method will include the comments as embedded obj
286
284
]
287
285
}
288
286
}
289
- </pre ></code >
287
+ </code ></pre >
290
288
291
289
Rails uses the same logic to generate embedded serializations as it does when you use +render :json+. In this case,
292
290
because you didn't define a +CommentSerializer+, Rails used the default +as_json+ on your comment object.
293
291
294
292
If you define a serializer, Rails will automatically instantiate it with the existing authorization scope.
295
293
296
- <code><pre >
294
+ <pre><code >
297
295
class CommentSerializer
298
296
def initialize(comment, scope)
299
297
@comment, @scope = comment, scope
@@ -307,26 +305,26 @@ class CommentSerializer
307
305
{ comment: serializable_hash }
308
306
end
309
307
end
310
- </pre ></code >
308
+ </code ></pre >
311
309
312
310
If we define the above comment serializer, the outputted JSON will change to:
313
311
314
- <code><pre >
312
+ <pre><code >
315
313
{
316
314
post: {
317
315
title: "Hello Blog!",
318
316
body: "This is my first post. Isn't it fabulous!",
319
317
comments: [{ title: "Awesome" }]
320
318
}
321
319
}
322
- </pre ></code >
320
+ </code ></pre >
323
321
324
322
Let's imagine that our comment system allows an administrator to kill a comment, and we only want to allow
325
323
users to see the comments they're entitled to see. By default, +has_many :comments+ will simply use the
326
324
+comments+ accessor on the post object. We can override the +comments+ accessor to limit the comments used
327
325
to just the comments we want to allow for the current user.
328
326
329
- <code><pre >
327
+ <pre><code >
330
328
class PostSerializer < ActiveModel::Serializer
331
329
attributes :title. :body
332
330
has_many :comments
@@ -346,7 +344,7 @@ private
346
344
@scope.superuser?
347
345
end
348
346
end
349
- </pre ></code >
347
+ </code ></pre >
350
348
351
349
+ActiveModel::Serializer+ will still embed the comments, but this time it will use just the comments
352
350
for the current user.
@@ -361,7 +359,7 @@ build up the hash manually.
361
359
362
360
For example, let's say our front-end expects the posts and comments in the following format:
363
361
364
- <code><pre >
362
+ <pre><code >
365
363
{
366
364
post: {
367
365
id: 1
@@ -382,11 +380,11 @@ For example, let's say our front-end expects the posts and comments in the follo
382
380
}
383
381
]
384
382
}
385
- </pre ></code >
383
+ </code ></pre >
386
384
387
385
We could achieve this with a custom +as_json+ method. We will also need to define a serializer for comments.
388
386
389
- <code><pre >
387
+ <pre><code >
390
388
class CommentSerializer < ActiveModel::Serializer
391
389
attributes :id, :title, :body
392
390
@@ -422,7 +420,7 @@ private
422
420
@scope.superuser?
423
421
end
424
422
end
425
- </pre ></code >
423
+ </code ></pre >
426
424
427
425
Here, we used two convenience methods: +associations+ and +association_ids+. The first,
428
426
+associations+, creates a hash of all of the define associations, using their defined
@@ -444,7 +442,7 @@ For instance, we might want to provide the full comment when it is requested dir
444
442
but only its title when requested as part of the post. To achieve this, you can define
445
443
a serializer for associated objects nested inside the main serializer.
446
444
447
- <code><pre >
445
+ <pre><code >
448
446
class PostSerializer < ActiveModel::Serializer
449
447
class CommentSerializer < ActiveModel::Serializer
450
448
attributes :id, :title
@@ -453,7 +451,7 @@ class PostSerializer < ActiveModel::Serializer
453
451
# same as before
454
452
# ...
455
453
end
456
- </pre ></code >
454
+ </code ></pre >
457
455
458
456
In other words, if a +PostSerializer+ is trying to serialize comments, it will first
459
457
look for +PostSerializer::CommentSerializer+ before falling back to +CommentSerializer+
@@ -470,11 +468,11 @@ its +current_user+ method and pass that along to the serializer's initializer.
470
468
If you want to change that behavior, simply use the +serialization_scope+ class
471
469
method.
472
470
473
- <code><pre >
471
+ <pre><code >
474
472
class PostsController < ApplicationController
475
473
serialization_scope :current_app
476
474
end
477
- </pre ></code >
475
+ </code ></pre >
478
476
479
477
You can also implement an instance method called (no surprise) +serialization_scope+,
480
478
which allows you to define a dynamic authorization scope based on the current request.
@@ -491,19 +489,19 @@ outside a request.
491
489
For instance, if you want to generate the JSON representation of a post for a user outside
492
490
of a request:
493
491
494
- <code><pre >
492
+ <pre><code >
495
493
user = get_user # some logic to get the user in question
496
494
PostSerializer.new(post, user).to_json # reliably generate JSON output
497
- </pre ></code >
495
+ </code ></pre >
498
496
499
497
If you want to generate JSON for an anonymous user, you should be able to use whatever
500
498
technique you use in your application to generate anonymous users outside of a request.
501
499
Typically, that means creating a new user and not saving it to the database:
502
500
503
- <code><pre >
501
+ <pre><code >
504
502
user = User.new # create a new anonymous user
505
503
PostSerializer.new(post, user).to_json
506
- </pre ></code >
504
+ </code ></pre >
507
505
508
506
In general, the better you encapsulate your authorization logic, the more easily you
509
507
will be able to use the serializer outside of the context of a request. For instance,
@@ -521,7 +519,7 @@ as the root).
521
519
522
520
For example, an Array of post objects would serialize as:
523
521
524
- <code><pre >
522
+ <pre><code >
525
523
{
526
524
posts: [
527
525
{
@@ -533,12 +531,12 @@ For example, an Array of post objects would serialize as:
533
531
}
534
532
]
535
533
}
536
- </pre ></code >
534
+ </code ></pre >
537
535
538
536
If you want to change the behavior of serialized Arrays, you need to create
539
537
a custom Array serializer.
540
538
541
- <code><pre >
539
+ <pre><code >
542
540
class ArraySerializer < ActiveModel::ArraySerializer
543
541
def serializable_array
544
542
serializers.map do |serializer|
@@ -552,7 +550,7 @@ class ArraySerializer < ActiveModel::ArraySerializer
552
550
hash
553
551
end
554
552
end
555
- </pre ></code >
553
+ </code ></pre >
556
554
557
555
When generating embedded associations using the +associations+ helper inside a
558
556
regular serializer, it will create a new <code>ArraySerializer</code> with the
0 commit comments