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