active_model_serializers 0.9.13 → 0.10.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +21 -0
  3. data/.travis.yml +27 -0
  4. data/CHANGELOG.md +8 -236
  5. data/CONTRIBUTING.md +23 -12
  6. data/Gemfile +17 -0
  7. data/{MIT-LICENSE → LICENSE.txt} +3 -2
  8. data/README.md +151 -781
  9. data/Rakefile +12 -0
  10. data/active_model_serializers.gemspec +26 -0
  11. data/lib/action_controller/serialization.rb +30 -84
  12. data/lib/active_model/serializer/adapter/fragment_cache.rb +78 -0
  13. data/lib/active_model/serializer/adapter/json/fragment_cache.rb +15 -0
  14. data/lib/active_model/serializer/adapter/json.rb +52 -0
  15. data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +22 -0
  16. data/lib/active_model/serializer/adapter/json_api.rb +152 -0
  17. data/lib/active_model/serializer/adapter/null.rb +11 -0
  18. data/lib/active_model/serializer/adapter.rb +87 -0
  19. data/lib/active_model/serializer/array_serializer.rb +32 -0
  20. data/lib/active_model/serializer/configuration.rb +13 -0
  21. data/lib/active_model/serializer/fieldset.rb +40 -0
  22. data/lib/active_model/serializer/version.rb +1 -3
  23. data/lib/active_model/serializer.rb +193 -276
  24. data/lib/active_model_serializers.rb +5 -19
  25. data/lib/generators/serializer/USAGE +6 -0
  26. data/lib/{active_model/serializer/generators → generators}/serializer/serializer_generator.rb +8 -10
  27. data/lib/{active_model/serializer/generators → generators}/serializer/templates/serializer.rb +2 -2
  28. data/test/action_controller/adapter_selector_test.rb +51 -0
  29. data/test/action_controller/explicit_serializer_test.rb +110 -0
  30. data/test/action_controller/json_api_linked_test.rb +173 -0
  31. data/test/action_controller/serialization_scope_name_test.rb +63 -0
  32. data/test/action_controller/serialization_test.rb +365 -0
  33. data/test/adapter/fragment_cache_test.rb +27 -0
  34. data/test/adapter/json/belongs_to_test.rb +41 -0
  35. data/test/adapter/json/collection_test.rb +59 -0
  36. data/test/adapter/json/has_many_test.rb +36 -0
  37. data/test/adapter/json_api/belongs_to_test.rb +147 -0
  38. data/test/adapter/json_api/collection_test.rb +89 -0
  39. data/test/adapter/json_api/has_many_embed_ids_test.rb +45 -0
  40. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +98 -0
  41. data/test/adapter/json_api/has_many_test.rb +106 -0
  42. data/test/adapter/json_api/has_one_test.rb +59 -0
  43. data/test/adapter/json_api/linked_test.rb +257 -0
  44. data/test/adapter/json_test.rb +34 -0
  45. data/test/adapter/null_test.rb +25 -0
  46. data/test/adapter_test.rb +43 -0
  47. data/test/array_serializer_test.rb +29 -0
  48. data/test/fixtures/poro.rb +123 -172
  49. data/test/serializers/adapter_for_test.rb +50 -0
  50. data/test/serializers/associations_test.rb +106 -0
  51. data/test/serializers/attribute_test.rb +23 -0
  52. data/test/serializers/attributes_test.rb +28 -0
  53. data/test/serializers/cache_test.rb +128 -0
  54. data/test/serializers/configuration_test.rb +15 -0
  55. data/test/serializers/fieldset_test.rb +26 -0
  56. data/test/serializers/generators_test.rb +59 -0
  57. data/test/serializers/meta_test.rb +78 -0
  58. data/test/serializers/options_test.rb +21 -0
  59. data/test/serializers/serializer_for_test.rb +56 -0
  60. data/test/serializers/urls_test.rb +26 -0
  61. data/test/test_helper.rb +20 -10
  62. metadata +121 -148
  63. data/DESIGN.textile +0 -586
  64. data/lib/action_controller/serialization_test_case.rb +0 -82
  65. data/lib/active_model/array_serializer.rb +0 -70
  66. data/lib/active_model/default_serializer.rb +0 -30
  67. data/lib/active_model/serializable/utils.rb +0 -18
  68. data/lib/active_model/serializable.rb +0 -61
  69. data/lib/active_model/serializer/association/has_many.rb +0 -41
  70. data/lib/active_model/serializer/association/has_one.rb +0 -27
  71. data/lib/active_model/serializer/association.rb +0 -58
  72. data/lib/active_model/serializer/config.rb +0 -33
  73. data/lib/active_model/serializer/generators/resource_override.rb +0 -15
  74. data/lib/active_model/serializer/generators/serializer/USAGE +0 -9
  75. data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +0 -16
  76. data/lib/active_model/serializer/generators/serializer/templates/controller.rb +0 -93
  77. data/lib/active_model/serializer/railtie.rb +0 -24
  78. data/lib/active_model/serializer_support.rb +0 -7
  79. data/test/benchmark/app.rb +0 -60
  80. data/test/benchmark/benchmarking_support.rb +0 -67
  81. data/test/benchmark/bm_active_record.rb +0 -41
  82. data/test/benchmark/setup.rb +0 -75
  83. data/test/benchmark/tmp/miniprofiler/mp_timers_6eqewtfgrhitvq5gqm25 +0 -0
  84. data/test/benchmark/tmp/miniprofiler/mp_timers_8083sx03hu72pxz1a4d0 +0 -0
  85. data/test/benchmark/tmp/miniprofiler/mp_timers_fyz2gsml4z0ph9kpoy1c +0 -0
  86. data/test/benchmark/tmp/miniprofiler/mp_timers_hjry5rc32imd42oxoi48 +0 -0
  87. data/test/benchmark/tmp/miniprofiler/mp_timers_m8fpoz2cvt3g9agz0bs3 +0 -0
  88. data/test/benchmark/tmp/miniprofiler/mp_timers_p92m2drnj1i568u3sta0 +0 -0
  89. data/test/benchmark/tmp/miniprofiler/mp_timers_qg52tpca3uesdfguee9i +0 -0
  90. data/test/benchmark/tmp/miniprofiler/mp_timers_s15t1a6mvxe0z7vjv790 +0 -0
  91. data/test/benchmark/tmp/miniprofiler/mp_timers_x8kal3d17nfds6vp4kcj +0 -0
  92. data/test/benchmark/tmp/miniprofiler/mp_views_127.0.0.1 +0 -0
  93. data/test/fixtures/active_record.rb +0 -96
  94. data/test/fixtures/template.html.erb +0 -1
  95. data/test/integration/action_controller/namespaced_serialization_test.rb +0 -105
  96. data/test/integration/action_controller/serialization_test.rb +0 -287
  97. data/test/integration/action_controller/serialization_test_case_test.rb +0 -71
  98. data/test/integration/active_record/active_record_test.rb +0 -94
  99. data/test/integration/generators/resource_generator_test.rb +0 -26
  100. data/test/integration/generators/scaffold_controller_generator_test.rb +0 -64
  101. data/test/integration/generators/serializer_generator_test.rb +0 -41
  102. data/test/test_app.rb +0 -18
  103. data/test/tmp/app/serializers/account_serializer.rb +0 -3
  104. data/test/unit/active_model/array_serializer/except_test.rb +0 -18
  105. data/test/unit/active_model/array_serializer/key_format_test.rb +0 -18
  106. data/test/unit/active_model/array_serializer/meta_test.rb +0 -53
  107. data/test/unit/active_model/array_serializer/only_test.rb +0 -18
  108. data/test/unit/active_model/array_serializer/options_test.rb +0 -16
  109. data/test/unit/active_model/array_serializer/root_test.rb +0 -102
  110. data/test/unit/active_model/array_serializer/scope_test.rb +0 -24
  111. data/test/unit/active_model/array_serializer/serialization_test.rb +0 -239
  112. data/test/unit/active_model/default_serializer_test.rb +0 -13
  113. data/test/unit/active_model/serializer/associations/build_serializer_test.rb +0 -36
  114. data/test/unit/active_model/serializer/associations_test.rb +0 -49
  115. data/test/unit/active_model/serializer/attributes_test.rb +0 -57
  116. data/test/unit/active_model/serializer/config_test.rb +0 -91
  117. data/test/unit/active_model/serializer/filter_test.rb +0 -69
  118. data/test/unit/active_model/serializer/has_many_polymorphic_test.rb +0 -189
  119. data/test/unit/active_model/serializer/has_many_test.rb +0 -265
  120. data/test/unit/active_model/serializer/has_one_and_has_many_test.rb +0 -27
  121. data/test/unit/active_model/serializer/has_one_polymorphic_test.rb +0 -196
  122. data/test/unit/active_model/serializer/has_one_test.rb +0 -253
  123. data/test/unit/active_model/serializer/key_format_test.rb +0 -25
  124. data/test/unit/active_model/serializer/meta_test.rb +0 -39
  125. data/test/unit/active_model/serializer/options_test.rb +0 -42
  126. data/test/unit/active_model/serializer/root_test.rb +0 -117
  127. data/test/unit/active_model/serializer/scope_test.rb +0 -49
  128. data/test/unit/active_model/serializer/url_helpers_test.rb +0 -36
  129. data/test/unit/active_model/serilizable_test.rb +0 -50
data/DESIGN.textile DELETED
@@ -1,586 +0,0 @@
1
- <strong>This was the original design document for serializers.</strong> It is useful mostly for historical purposes as the public API has changed.
2
-
3
- h2. Rails Serializers
4
-
5
- This guide describes how to use Active Model serializers to build non-trivial JSON services in Rails. By reading this guide, you will learn:
6
-
7
- * When to use the built-in Active Model serialization
8
- * When to use a custom serializer for your models
9
- * How to use serializers to encapsulate authorization concerns
10
- * How to create serializer templates to describe the application-wide structure of your serialized JSON
11
- * How to build resources not backed by a single database table for use with JSON services
12
-
13
- This guide covers an intermediate topic and assumes familiarity with Rails conventions. It is suitable for applications that expose a
14
- JSON API that may return different results based on the authorization status of the user.
15
-
16
- h3. Serialization
17
-
18
- By default, Active Record objects can serialize themselves into JSON by using the `to_json` method. This method takes a series of additional
19
- parameter to control which properties and associations Rails should include in the serialized output.
20
-
21
- When building a web application that uses JavaScript to retrieve JSON data from the server, this mechanism has historically been the primary
22
- way that Rails developers prepared their responses. This works great for simple cases, as the logic for serializing an Active Record object
23
- is neatly encapsulated in Active Record itself.
24
-
25
- However, this solution quickly falls apart in the face of serialization requirements based on authorization. For instance, a web service
26
- may choose to expose additional information about a resource only if the user is entitled to access it. In addition, a JavaScript front-end
27
- may want information that is not neatly described in terms of serializing a single Active Record object, or in a different format than.
28
-
29
- In addition, neither the controller nor the model seems like the correct place for logic that describes how to serialize an model object
30
- *for the current user*.
31
-
32
- Serializers solve these problems by encapsulating serialization in an object designed for this purpose. If the default +to_json+ semantics,
33
- with at most a few configuration options serve your needs, by all means continue to use the built-in +to_json+. If you find yourself doing
34
- hash-driven-development in your controllers, juggling authorization logic and other concerns, serializers are for you!
35
-
36
- h3. The Most Basic Serializer
37
-
38
- A basic serializer is a simple Ruby object named after the model class it is serializing.
39
-
40
- <pre lang="ruby">
41
- class PostSerializer
42
- def initialize(post, scope)
43
- @post, @scope = post, scope
44
- end
45
-
46
- def as_json
47
- { post: { title: @post.name, body: @post.body } }
48
- end
49
- end
50
- </pre>
51
-
52
- A serializer is initialized with two parameters: the model object it should serialize and an authorization scope. By default, the
53
- authorization scope is the current user (+current_user+) but you can use a different object if you want. The serializer also
54
- implements an +as_json+ method, which returns a Hash that will be sent to the JSON encoder.
55
-
56
- Rails will transparently use your serializer when you use +render :json+ in your controller.
57
-
58
- <pre lang="ruby">
59
- class PostsController < ApplicationController
60
- def show
61
- @post = Post.find(params[:id])
62
- render json: @post
63
- end
64
- end
65
- </pre>
66
-
67
- Because +respond_with+ uses +render :json+ under the hood for JSON requests, Rails will automatically use your serializer when
68
- you use +respond_with+ as well.
69
-
70
- h4. +serializable_hash+
71
-
72
- In general, you will want to implement +serializable_hash+ and +as_json+ to allow serializers to embed associated content
73
- directly. The easiest way to implement these two methods is to have +as_json+ call +serializable_hash+ and insert the root.
74
-
75
- <pre lang="ruby">
76
- class PostSerializer
77
- def initialize(post, scope)
78
- @post, @scope = post, scope
79
- end
80
-
81
- def serializable_hash
82
- { title: @post.name, body: @post.body }
83
- end
84
-
85
- def as_json
86
- { post: serializable_hash }
87
- end
88
- end
89
- </pre>
90
-
91
- h4. Authorization
92
-
93
- 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
- access.
95
-
96
- <pre lang="ruby">
97
- class PostSerializer
98
- def initialize(post, scope)
99
- @post, @scope = post, scope
100
- end
101
-
102
- def as_json
103
- { post: serializable_hash }
104
- end
105
-
106
- def serializable_hash
107
- hash = post
108
- hash.merge!(super_data) if super?
109
- hash
110
- end
111
-
112
- private
113
- def post
114
- { title: @post.name, body: @post.body }
115
- end
116
-
117
- def super_data
118
- { email: @post.email }
119
- end
120
-
121
- def super?
122
- @scope.superuser?
123
- end
124
- end
125
- </pre>
126
-
127
- h4. Testing
128
-
129
- One benefit of encapsulating our objects this way is that it becomes extremely straight-forward to test the serialization
130
- logic in isolation.
131
-
132
- <pre lang="ruby">
133
- require "ostruct"
134
-
135
- class PostSerializerTest < ActiveSupport::TestCase
136
- # For now, we use a very simple authorization structure. These tests will need
137
- # refactoring if we change that.
138
- plebe = OpenStruct.new(super?: false)
139
- god = OpenStruct.new(super?: true)
140
-
141
- post = OpenStruct.new(title: "Welcome to my blog!", body: "Blah blah blah", email: "tenderlove@gmail.com")
142
-
143
- test "a regular user sees just the title and body" do
144
- json = PostSerializer.new(post, plebe).to_json
145
- hash = JSON.parse(json)
146
-
147
- assert_equal post.title, hash.delete("title")
148
- assert_equal post.body, hash.delete("body")
149
- assert_empty hash
150
- end
151
-
152
- test "a superuser sees the title, body and email" do
153
- json = PostSerializer.new(post, god).to_json
154
- hash = JSON.parse(json)
155
-
156
- assert_equal post.title, hash.delete("title")
157
- assert_equal post.body, hash.delete("body")
158
- assert_equal post.email, hash.delete("email")
159
- assert_empty hash
160
- end
161
- end
162
- </pre>
163
-
164
- It's important to note that serializer objects define a clear interface specifically for serializing an existing object.
165
- In this case, the serializer expects to receive a post object with +name+, +body+ and +email+ attributes and an authorization
166
- scope with a +super?+ method.
167
-
168
- By defining a clear interface, it's must easier to ensure that your authorization logic is behaving correctly. In this case,
169
- the serializer doesn't need to concern itself with how the authorization scope decides whether to set the +super?+ flag, just
170
- whether it is set. In general, you should document these requirements in your serializer files and programatically via tests.
171
- The documentation library +YARD+ provides excellent tools for describing this kind of requirement:
172
-
173
- <pre lang="ruby">
174
- class PostSerializer
175
- # @param [~body, ~title, ~email] post the post to serialize
176
- # @param [~super] scope the authorization scope for this serializer
177
- def initialize(post, scope)
178
- @post, @scope = post, scope
179
- end
180
-
181
- # ...
182
- end
183
- </pre>
184
-
185
- h3. Attribute Sugar
186
-
187
- To simplify this process for a number of common cases, Rails provides a default superclass named +ActiveModel::Serializer+
188
- that you can use to implement your serializers.
189
-
190
- For example, you will sometimes want to simply include a number of existing attributes from the source model into the outputted
191
- JSON. In the above example, the +title+ and +body+ attributes were always included in the JSON. Let's see how to use
192
- +ActiveModel::Serializer+ to simplify our post serializer.
193
-
194
- <pre lang="ruby">
195
- class PostSerializer < ActiveModel::Serializer
196
- attributes :title, :body
197
-
198
- def serializable_hash
199
- hash = attributes
200
- hash.merge!(super_data) if super?
201
- hash
202
- end
203
-
204
- private
205
- def super_data
206
- { email: @post.email }
207
- end
208
-
209
- def super?
210
- @scope.superuser?
211
- end
212
- end
213
- </pre>
214
-
215
- First, we specified the list of included attributes at the top of the class. This will create an instance method called
216
- +attributes+ that extracts those attributes from the post model.
217
-
218
- NOTE: Internally, +ActiveModel::Serializer+ uses +read_attribute_for_serialization+, which defaults to +read_attribute+, which defaults to +send+. So if you're rolling your own models for use with the serializer, you can use simple Ruby accessors for your attributes if you like.
219
-
220
- Next, we use the attributes method in our +serializable_hash+ method, which allowed us to eliminate the +post+ method we hand-rolled
221
- earlier. We could also eliminate the +as_json+ method, as +ActiveModel::Serializer+ provides a default +as_json+ method for
222
- us that calls our +serializable_hash+ method and inserts a root. But we can go a step further!
223
-
224
- <pre lang="ruby">
225
- class PostSerializer < ActiveModel::Serializer
226
- attributes :title, :body
227
-
228
- private
229
- def attributes
230
- hash = super
231
- hash.merge!(email: post.email) if super?
232
- hash
233
- end
234
-
235
- def super?
236
- @scope.superuser?
237
- end
238
- end
239
- </pre>
240
-
241
- The superclass provides a default +initialize+ method as well as a default +serializable_hash+ method, which uses
242
- +attributes+. We can call +super+ to get the hash based on the attributes we declared, and then add in any additional
243
- attributes we want to use.
244
-
245
- NOTE: +ActiveModel::Serializer+ will create an accessor matching the name of the current class for the resource you pass in. In this case, because we have defined a PostSerializer, we can access the resource with the +post+ accessor.
246
-
247
- h3. Associations
248
-
249
- In most JSON APIs, you will want to include associated objects with your serialized object. In this case, let's include
250
- the comments with the current post.
251
-
252
- <pre lang="ruby">
253
- class PostSerializer < ActiveModel::Serializer
254
- attributes :title, :body
255
- has_many :comments
256
-
257
- private
258
- def attributes
259
- hash = super
260
- hash.merge!(email: post.email) if super?
261
- hash
262
- end
263
-
264
- def super?
265
- @scope.superuser?
266
- end
267
- end
268
- </pre>
269
-
270
- The default +serializable_hash+ method will include the comments as embedded objects inside the post.
271
-
272
- <pre lang="json">
273
- {
274
- post: {
275
- title: "Hello Blog!",
276
- body: "This is my first post. Isn't it fabulous!",
277
- comments: [
278
- {
279
- title: "Awesome",
280
- body: "Your first post is great"
281
- }
282
- ]
283
- }
284
- }
285
- </pre>
286
-
287
- Rails uses the same logic to generate embedded serializations as it does when you use +render :json+. In this case,
288
- because you didn't define a +CommentSerializer+, Rails used the default +as_json+ on your comment object.
289
-
290
- If you define a serializer, Rails will automatically instantiate it with the existing authorization scope.
291
-
292
- <pre lang="ruby">
293
- class CommentSerializer
294
- def initialize(comment, scope)
295
- @comment, @scope = comment, scope
296
- end
297
-
298
- def serializable_hash
299
- { title: @comment.title }
300
- end
301
-
302
- def as_json
303
- { comment: serializable_hash }
304
- end
305
- end
306
- </pre>
307
-
308
- If we define the above comment serializer, the outputted JSON will change to:
309
-
310
- <pre lang="json">
311
- {
312
- post: {
313
- title: "Hello Blog!",
314
- body: "This is my first post. Isn't it fabulous!",
315
- comments: [{ title: "Awesome" }]
316
- }
317
- }
318
- </pre>
319
-
320
- Let's imagine that our comment system allows an administrator to kill a comment, and we only want to allow
321
- users to see the comments they're entitled to see. By default, +has_many :comments+ will simply use the
322
- +comments+ accessor on the post object. We can override the +comments+ accessor to limit the comments used
323
- to just the comments we want to allow for the current user.
324
-
325
- <pre lang="ruby">
326
- class PostSerializer < ActiveModel::Serializer
327
- attributes :title. :body
328
- has_many :comments
329
-
330
- private
331
- def attributes
332
- hash = super
333
- hash.merge!(email: post.email) if super?
334
- hash
335
- end
336
-
337
- def comments
338
- post.comments_for(scope)
339
- end
340
-
341
- def super?
342
- @scope.superuser?
343
- end
344
- end
345
- </pre>
346
-
347
- +ActiveModel::Serializer+ will still embed the comments, but this time it will use just the comments
348
- for the current user.
349
-
350
- NOTE: The logic for deciding which comments a user should see still belongs in the model layer. In general, you should encapsulate concerns that require making direct Active Record queries in scopes or public methods on your models.
351
-
352
- h4. Modifying Associations
353
-
354
- You can also rename associations if required. Say for example you have an association that
355
- makes sense to be named one thing in your code, but another when data is serialized.
356
- You can use the <code:key</code> option to specify a different name for an association.
357
- Here is an example:
358
-
359
- <pre lang="ruby">
360
- class UserSerializer < ActiveModel::Serializer
361
- has_many :followed_posts, key: :posts
362
- has_one :owned_account, key: :account
363
- end
364
- </pre>
365
-
366
- Using the <code>:key</code> without a <code>:serializer</code> option will use implicit detection
367
- to determine a serializer. In this example, you'd have to define two classes: <code>PostSerializer</code>
368
- and <code>AccountSerializer</code>. You can also add the <code>:serializer</code> option
369
- to set it explicitly:
370
-
371
- <pre lang="ruby">
372
- class UserSerializer < ActiveModel::Serializer
373
- has_many :followed_posts, key: :posts, serializer: CustomPostSerializer
374
- has_one :owne_account, key: :account, serializer: PrivateAccountSerializer
375
- end
376
- </pre>
377
-
378
- h3. Customizing Associations
379
-
380
- Not all front-ends expect embedded documents in the same form. In these cases, you can override the
381
- default +serializable_hash+, and use conveniences provided by +ActiveModel::Serializer+ to avoid having to
382
- build up the hash manually.
383
-
384
- For example, let's say our front-end expects the posts and comments in the following format:
385
-
386
- <pre lang="json">
387
- {
388
- post: {
389
- id: 1
390
- title: "Hello Blog!",
391
- body: "This is my first post. Isn't it fabulous!",
392
- comments: [1,2]
393
- },
394
- comments: [
395
- {
396
- id: 1
397
- title: "Awesome",
398
- body: "Your first post is great"
399
- },
400
- {
401
- id: 2
402
- title: "Not so awesome",
403
- body: "Why is it so short!"
404
- }
405
- ]
406
- }
407
- </pre>
408
-
409
- We could achieve this with a custom +as_json+ method. We will also need to define a serializer for comments.
410
-
411
- <pre lang="ruby">
412
- class CommentSerializer < ActiveModel::Serializer
413
- attributes :id, :title, :body
414
-
415
- # define any logic for dealing with authorization-based attributes here
416
- end
417
-
418
- class PostSerializer < ActiveModel::Serializer
419
- attributes :title, :body
420
- has_many :comments
421
-
422
- def as_json
423
- { post: serializable_hash }.merge!(associations)
424
- end
425
-
426
- def serializable_hash
427
- post_hash = attributes
428
- post_hash.merge!(association_ids)
429
- post_hash
430
- end
431
-
432
- private
433
- def attributes
434
- hash = super
435
- hash.merge!(email: post.email) if super?
436
- hash
437
- end
438
-
439
- def comments
440
- post.comments_for(scope)
441
- end
442
-
443
- def super?
444
- @scope.superuser?
445
- end
446
- end
447
- </pre>
448
-
449
- Here, we used two convenience methods: +associations+ and +association_ids+. The first,
450
- +associations+, creates a hash of all of the define associations, using their defined
451
- serializers. The second, +association_ids+, generates a hash whose key is the association
452
- name and whose value is an Array of the association's keys.
453
-
454
- The +association_ids+ helper will use the overridden version of the association, so in
455
- this case, +association_ids+ will only include the ids of the comments provided by the
456
- +comments+ method.
457
-
458
-
459
- h3. Special Association Serializers
460
-
461
- So far, associations defined in serializers use either the +as_json+ method on the model
462
- or the defined serializer for the association type. Sometimes, you may want to serialize
463
- associated models differently when they are requested as part of another resource than
464
- when they are requested on their own.
465
-
466
- For instance, we might want to provide the full comment when it is requested directly,
467
- but only its title when requested as part of the post. To achieve this, you can define
468
- a serializer for associated objects nested inside the main serializer.
469
-
470
- <pre lang="ruby">
471
- class PostSerializer < ActiveModel::Serializer
472
- class CommentSerializer < ActiveModel::Serializer
473
- attributes :id, :title
474
- end
475
-
476
- # same as before
477
- # ...
478
- end
479
- </pre>
480
-
481
- In other words, if a +PostSerializer+ is trying to serialize comments, it will first
482
- look for +PostSerializer::CommentSerializer+ before falling back to +CommentSerializer+
483
- and finally +comment.as_json+.
484
-
485
- h3. Overriding the Defaults
486
-
487
- h4. Authorization Scope
488
-
489
- By default, the authorization scope for serializers is +:current_user+. This means
490
- that when you call +render json: @post+, the controller will automatically call
491
- its +current_user+ method and pass that along to the serializer's initializer.
492
-
493
- If you want to change that behavior, simply use the +serialization_scope+ class
494
- method.
495
-
496
- <pre lang="ruby">
497
- class PostsController < ApplicationController
498
- serialization_scope :current_app
499
- end
500
- </pre>
501
-
502
- You can also implement an instance method called (no surprise) +serialization_scope+,
503
- which allows you to define a dynamic authorization scope based on the current request.
504
-
505
- WARNING: If you use different objects as authorization scopes, make sure that they all implement whatever interface you use in your serializers to control what the outputted JSON looks like.
506
-
507
- h3. Using Serializers Outside of a Request
508
-
509
- The serialization API encapsulates the concern of generating a JSON representation of
510
- a particular model for a particular user. As a result, you should be able to easily use
511
- serializers, whether you define them yourself or whether you use +ActiveModel::Serializer+
512
- outside a request.
513
-
514
- For instance, if you want to generate the JSON representation of a post for a user outside
515
- of a request:
516
-
517
- <pre lang="ruby">
518
- user = get_user # some logic to get the user in question
519
- PostSerializer.new(post, user).to_json # reliably generate JSON output
520
- </pre>
521
-
522
- If you want to generate JSON for an anonymous user, you should be able to use whatever
523
- technique you use in your application to generate anonymous users outside of a request.
524
- Typically, that means creating a new user and not saving it to the database:
525
-
526
- <pre lang="ruby">
527
- user = User.new # create a new anonymous user
528
- PostSerializer.new(post, user).to_json
529
- </pre>
530
-
531
- In general, the better you encapsulate your authorization logic, the more easily you
532
- will be able to use the serializer outside of the context of a request. For instance,
533
- if you use an authorization library like Cancan, which uses a uniform +user.can?(action, model)+,
534
- the authorization interface can very easily be replaced by a plain Ruby object for
535
- testing or usage outside the context of a request.
536
-
537
- h3. Collections
538
-
539
- So far, we've talked about serializing individual model objects. By default, Rails
540
- will serialize collections, including when using the +associations+ helper, by
541
- looping over each element of the collection, calling +serializable_hash+ on the element,
542
- and then grouping them by their type (using the plural version of their class name
543
- as the root).
544
-
545
- For example, an Array of post objects would serialize as:
546
-
547
- <pre lang="json">
548
- {
549
- posts: [
550
- {
551
- title: "FIRST POST!",
552
- body: "It's my first pooooost"
553
- },
554
- { title: "Second post!",
555
- body: "Zomg I made it to my second post"
556
- }
557
- ]
558
- }
559
- </pre>
560
-
561
- If you want to change the behavior of serialized Arrays, you need to create
562
- a custom Array serializer.
563
-
564
- <pre lang="ruby">
565
- class ArraySerializer < ActiveModel::ArraySerializer
566
- def serializable_array
567
- serializers.map do |serializer|
568
- serializer.serializable_hash
569
- end
570
- end
571
-
572
- def as_json
573
- hash = { root => serializable_array }
574
- hash.merge!(associations)
575
- hash
576
- end
577
- end
578
- </pre>
579
-
580
- When generating embedded associations using the +associations+ helper inside a
581
- regular serializer, it will create a new <code>ArraySerializer</code> with the
582
- associated content and call its +serializable_array+ method. In this case, those
583
- embedded associations will not recursively include associations.
584
-
585
- When generating an Array using +render json: posts+, the controller will invoke
586
- the +as_json+ method, which will include its associations and its root.
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionController
4
- module SerializationAssertions
5
- extend ActiveSupport::Concern
6
-
7
- included do
8
- setup :setup_serialization_subscriptions
9
- teardown :teardown_serialization_subscriptions
10
- end
11
-
12
- def setup_serialization_subscriptions
13
- @serializers = Hash.new(0)
14
-
15
- ActiveSupport::Notifications.subscribe("!serialize.active_model_serializers") do |name, start, finish, id, payload|
16
- serializer = payload[:serializer]
17
- @serializers[serializer] += 1
18
- end
19
- end
20
-
21
- def teardown_serialization_subscriptions
22
- ActiveSupport::Notifications.unsubscribe("!serialize.active_model_serializers")
23
- end
24
-
25
- def process(*args)
26
- @serializers = Hash.new(0)
27
- super
28
- end
29
- ruby2_keywords :process if respond_to?(:ruby2_keywords, true)
30
-
31
- # Asserts that the request was rendered with the appropriate serializers.
32
- #
33
- # # assert that the "PostSerializer" serializer was rendered
34
- # assert_serializer "PostSerializer"
35
- #
36
- # # assert that the instance of PostSerializer was rendered
37
- # assert_serializer PostSerializer
38
- #
39
- # # assert that the "PostSerializer" serializer was rendered
40
- # assert_serializer :post_serializer
41
- #
42
- # # assert that the rendered serializer starts with "Post"
43
- # assert_serializer %r{\APost.+\Z}
44
- #
45
- # # assert that no serializer was rendered
46
- # assert_serializer nil
47
- #
48
- #
49
- def assert_serializer(options = {}, message = nil)
50
- # Force body to be read in case the template is being streamed.
51
- response.body
52
-
53
- rendered = @serializers
54
- msg = message || "expecting <#{options.inspect}> but rendering with <#{rendered.keys}>"
55
-
56
- matches_serializer = case options
57
- when lambda { |options| options.kind_of?(Class) && options < ActiveModel::Serializer }
58
- rendered.any? do |serializer, count|
59
- options.name == serializer
60
- end
61
- when Symbol
62
- options = options.to_s.camelize
63
- rendered.any? do |serializer, count|
64
- serializer == options
65
- end
66
- when String
67
- !options.empty? && rendered.any? do |serializer, count|
68
- serializer == options
69
- end
70
- when Regexp
71
- rendered.any? do |serializer, count|
72
- serializer.match(options)
73
- end
74
- when NilClass
75
- rendered.blank?
76
- else
77
- raise ArgumentError, "assert_serializer only accepts a String, Symbol, Regexp, ActiveModel::Serializer, or nil"
78
- end
79
- assert matches_serializer, msg
80
- end
81
- end
82
- end