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
@@ -0,0 +1,535 @@
1
+ # frozen_string_literal: true
2
+
3
+ # {http://jsonapi.org/format/ JSON API specification}
4
+ # rubocop:disable Style/AsciiComments
5
+ # TODO: implement!
6
+ # ☐ https://github.com/rails-api/active_model_serializers/issues/1235
7
+ # TODO: use uri_template in link generation?
8
+ # ☐ https://github.com/rails-api/active_model_serializers/pull/1282#discussion_r42528812
9
+ # see gem https://github.com/hannesg/uri_template
10
+ # spec http://tools.ietf.org/html/rfc6570
11
+ # impl https://developer.github.com/v3/#schema https://api.github.com/
12
+ # TODO: validate against a JSON schema document?
13
+ # ☐ https://github.com/rails-api/active_model_serializers/issues/1162
14
+ # ☑ https://github.com/rails-api/active_model_serializers/pull/1270
15
+ # TODO: Routing
16
+ # ☐ https://github.com/rails-api/active_model_serializers/pull/1476
17
+ # TODO: Query Params
18
+ # ☑ `include` https://github.com/rails-api/active_model_serializers/pull/1131
19
+ # ☑ `fields` https://github.com/rails-api/active_model_serializers/pull/700
20
+ # ☑ `page[number]=3&page[size]=1` https://github.com/rails-api/active_model_serializers/pull/1041
21
+ # ☐ `filter`
22
+ # ☐ `sort`
23
+ module ActiveModelSerializers
24
+ module Adapter
25
+ class JsonApi < Base
26
+ extend ActiveSupport::Autoload
27
+ eager_autoload do
28
+ autoload :Jsonapi
29
+ autoload :ResourceIdentifier
30
+ autoload :Link
31
+ autoload :PaginationLinks
32
+ autoload :Meta
33
+ autoload :Error
34
+ autoload :Deserialization
35
+ autoload :Relationship
36
+ end
37
+
38
+ def self.default_key_transform
39
+ :dash
40
+ end
41
+
42
+ def self.fragment_cache(cached_hash, non_cached_hash, root = true)
43
+ core_cached = cached_hash.first
44
+ core_non_cached = non_cached_hash.first
45
+ no_root_cache = cached_hash.delete_if { |key, _value| key == core_cached[0] }
46
+ no_root_non_cache = non_cached_hash.delete_if { |key, _value| key == core_non_cached[0] }
47
+ cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
48
+ hash = root ? { root => cached_resource } : cached_resource
49
+
50
+ hash.deep_merge no_root_non_cache.deep_merge no_root_cache
51
+ end
52
+
53
+ def initialize(serializer, options = {})
54
+ super
55
+ @include_directive = JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
56
+ @fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
57
+ end
58
+
59
+ # {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure}
60
+ # {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.}
61
+ def serializable_hash(*)
62
+ document = if serializer.success?
63
+ success_document
64
+ else
65
+ failure_document
66
+ end
67
+ self.class.transform_key_casing!(document, instance_options)
68
+ end
69
+
70
+ def fragment_cache(cached_hash, non_cached_hash)
71
+ root = !instance_options.include?(:include)
72
+ self.class.fragment_cache(cached_hash, non_cached_hash, root)
73
+ end
74
+
75
+ # {http://jsonapi.org/format/#document-top-level Primary data}
76
+ # definition:
77
+ # ☐ toplevel_data (required)
78
+ # ☐ toplevel_included
79
+ # ☑ toplevel_meta
80
+ # ☑ toplevel_links
81
+ # ☑ toplevel_jsonapi
82
+ # structure:
83
+ # {
84
+ # data: toplevel_data,
85
+ # included: toplevel_included,
86
+ # meta: toplevel_meta,
87
+ # links: toplevel_links,
88
+ # jsonapi: toplevel_jsonapi
89
+ # }.reject! {|_,v| v.nil? }
90
+ # rubocop:disable Metrics/CyclomaticComplexity
91
+ def success_document
92
+ is_collection = serializer.respond_to?(:each)
93
+ serializers = is_collection ? serializer : [serializer]
94
+ primary_data, included = resource_objects_for(serializers)
95
+
96
+ hash = {}
97
+ # toplevel_data
98
+ # definition:
99
+ # oneOf
100
+ # resource
101
+ # array of unique items of type 'resource'
102
+ # null
103
+ #
104
+ # description:
105
+ # The document's "primary data" is a representation of the resource or collection of resources
106
+ # targeted by a request.
107
+ #
108
+ # Singular: the resource object.
109
+ #
110
+ # Collection: one of an array of resource objects, an array of resource identifier objects, or
111
+ # an empty array ([]), for requests that target resource collections.
112
+ #
113
+ # None: null if the request is one that might correspond to a single resource, but doesn't currently.
114
+ # structure:
115
+ # if serializable_resource.resource?
116
+ # resource
117
+ # elsif serializable_resource.collection?
118
+ # [
119
+ # resource,
120
+ # resource
121
+ # ]
122
+ # else
123
+ # nil
124
+ # end
125
+ hash[:data] = is_collection ? primary_data : primary_data[0]
126
+ # toplevel_included
127
+ # alias included
128
+ # definition:
129
+ # array of unique items of type 'resource'
130
+ #
131
+ # description:
132
+ # To reduce the number of HTTP requests, servers **MAY** allow
133
+ # responses that include related resources along with the requested primary
134
+ # resources. Such responses are called "compound documents".
135
+ # structure:
136
+ # [
137
+ # resource,
138
+ # resource
139
+ # ]
140
+ hash[:included] = included if included.any?
141
+
142
+ Jsonapi.add!(hash)
143
+
144
+ if instance_options[:links]
145
+ hash[:links] ||= {}
146
+ hash[:links].update(instance_options[:links])
147
+ end
148
+
149
+ if is_collection && serializer.paginated?
150
+ hash[:links] ||= {}
151
+ hash[:links].update(pagination_links_for(serializer))
152
+ end
153
+
154
+ hash[:meta] = instance_options[:meta] unless instance_options[:meta].blank?
155
+
156
+ hash
157
+ end
158
+ # rubocop:enable Metrics/CyclomaticComplexity
159
+
160
+ # {http://jsonapi.org/format/#errors JSON API Errors}
161
+ # TODO: look into caching
162
+ # definition:
163
+ # ☑ toplevel_errors array (required)
164
+ # ☐ toplevel_meta
165
+ # ☐ toplevel_jsonapi
166
+ # structure:
167
+ # {
168
+ # errors: toplevel_errors,
169
+ # meta: toplevel_meta,
170
+ # jsonapi: toplevel_jsonapi
171
+ # }.reject! {|_,v| v.nil? }
172
+ # prs:
173
+ # https://github.com/rails-api/active_model_serializers/pull/1004
174
+ def failure_document
175
+ hash = {}
176
+ # PR Please :)
177
+ # Jsonapi.add!(hash)
178
+
179
+ # toplevel_errors
180
+ # definition:
181
+ # array of unique items of type 'error'
182
+ # structure:
183
+ # [
184
+ # error,
185
+ # error
186
+ # ]
187
+ if serializer.respond_to?(:each)
188
+ hash[:errors] = serializer.flat_map do |error_serializer|
189
+ Error.resource_errors(error_serializer, instance_options)
190
+ end
191
+ else
192
+ hash[:errors] = Error.resource_errors(serializer, instance_options)
193
+ end
194
+ hash
195
+ end
196
+
197
+ protected
198
+
199
+ attr_reader :fieldset
200
+
201
+ private
202
+
203
+ # {http://jsonapi.org/format/#document-resource-objects Primary data}
204
+ # resource
205
+ # definition:
206
+ # JSON Object
207
+ #
208
+ # properties:
209
+ # type (required) : String
210
+ # id (required) : String
211
+ # attributes
212
+ # relationships
213
+ # links
214
+ # meta
215
+ #
216
+ # description:
217
+ # "Resource objects" appear in a JSON API document to represent resources
218
+ # structure:
219
+ # {
220
+ # type: 'admin--some-user',
221
+ # id: '1336',
222
+ # attributes: attributes,
223
+ # relationships: relationships,
224
+ # links: links,
225
+ # meta: meta,
226
+ # }.reject! {|_,v| v.nil? }
227
+ # prs:
228
+ # type
229
+ # https://github.com/rails-api/active_model_serializers/pull/1122
230
+ # [x] https://github.com/rails-api/active_model_serializers/pull/1213
231
+ # https://github.com/rails-api/active_model_serializers/pull/1216
232
+ # https://github.com/rails-api/active_model_serializers/pull/1029
233
+ # links
234
+ # [x] https://github.com/rails-api/active_model_serializers/pull/1246
235
+ # [x] url helpers https://github.com/rails-api/active_model_serializers/issues/1269
236
+ # meta
237
+ # [x] https://github.com/rails-api/active_model_serializers/pull/1340
238
+ def resource_objects_for(serializers)
239
+ @primary = []
240
+ @included = []
241
+ @resource_identifiers = Set.new
242
+ serializers.each { |serializer| process_resource(serializer, true, @include_directive) }
243
+ serializers.each { |serializer| process_relationships(serializer, @include_directive) }
244
+
245
+ [@primary, @included]
246
+ end
247
+
248
+ def process_resource(serializer, primary, include_slice = {})
249
+ resource_identifier = ResourceIdentifier.new(serializer, instance_options).as_json
250
+ return false unless @resource_identifiers.add?(resource_identifier)
251
+
252
+ resource_object = resource_object_for(serializer, include_slice)
253
+ if primary
254
+ @primary << resource_object
255
+ else
256
+ @included << resource_object
257
+ end
258
+
259
+ true
260
+ end
261
+
262
+ def process_relationships(serializer, include_slice)
263
+ serializer.associations(include_slice).each do |association|
264
+ # TODO(BF): Process relationship without evaluating lazy_association
265
+ process_relationship(association.lazy_association.serializer, include_slice[association.key])
266
+ end
267
+ end
268
+
269
+ def process_relationship(serializer, include_slice)
270
+ if serializer.respond_to?(:each)
271
+ serializer.each { |s| process_relationship(s, include_slice) }
272
+ return
273
+ end
274
+ return unless serializer && serializer.object
275
+ return unless process_resource(serializer, false, include_slice)
276
+
277
+ process_relationships(serializer, include_slice)
278
+ end
279
+
280
+ # {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes}
281
+ # attributes
282
+ # definition:
283
+ # JSON Object
284
+ #
285
+ # patternProperties:
286
+ # ^(?!relationships$|links$)\\w[-\\w_]*$
287
+ #
288
+ # description:
289
+ # Members of the attributes object ("attributes") represent information about the resource
290
+ # object in which it's defined.
291
+ # Attributes may contain any valid JSON value
292
+ # structure:
293
+ # {
294
+ # foo: 'bar'
295
+ # }
296
+ def attributes_for(serializer, fields)
297
+ serializer.attributes(fields).except(:id)
298
+ end
299
+
300
+ # {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
301
+ def resource_object_for(serializer, include_slice = {})
302
+ resource_object = data_for(serializer, include_slice)
303
+
304
+ # toplevel_links
305
+ # definition:
306
+ # allOf
307
+ # ☐ links
308
+ # ☐ pagination
309
+ #
310
+ # description:
311
+ # Link members related to the primary data.
312
+ # structure:
313
+ # links.merge!(pagination)
314
+ # prs:
315
+ # https://github.com/rails-api/active_model_serializers/pull/1247
316
+ # https://github.com/rails-api/active_model_serializers/pull/1018
317
+ if (links = links_for(serializer)).any?
318
+ resource_object ||= {}
319
+ resource_object[:links] = links
320
+ end
321
+
322
+ # toplevel_meta
323
+ # alias meta
324
+ # definition:
325
+ # meta
326
+ # structure
327
+ # {
328
+ # :'git-ref' => 'abc123'
329
+ # }
330
+ if (meta = meta_for(serializer)).present?
331
+ resource_object ||= {}
332
+ resource_object[:meta] = meta
333
+ end
334
+
335
+ resource_object
336
+ end
337
+
338
+ def data_for(serializer, include_slice)
339
+ data = serializer.fetch(self) do
340
+ resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
341
+ break nil if resource_object.nil?
342
+
343
+ requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
344
+ attributes = attributes_for(serializer, requested_fields)
345
+ resource_object[:attributes] = attributes if attributes.any?
346
+ resource_object
347
+ end
348
+ data.tap do |resource_object|
349
+ next if resource_object.nil?
350
+ # NOTE(BF): the attributes are cached above, separately from the relationships, below.
351
+ requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
352
+ relationships = relationships_for(serializer, requested_associations, include_slice)
353
+ resource_object[:relationships] = relationships if relationships.any?
354
+ end
355
+ end
356
+
357
+ # {http://jsonapi.org/format/#document-resource-object-relationships Document Resource Object Relationship}
358
+ # relationships
359
+ # definition:
360
+ # JSON Object
361
+ #
362
+ # patternProperties:
363
+ # ^\\w[-\\w_]*$"
364
+ #
365
+ # properties:
366
+ # data : relationshipsData
367
+ # links
368
+ # meta
369
+ #
370
+ # description:
371
+ #
372
+ # Members of the relationships object ("relationships") represent references from the
373
+ # resource object in which it's defined to other resource objects."
374
+ # structure:
375
+ # {
376
+ # links: links,
377
+ # meta: meta,
378
+ # data: relationshipsData
379
+ # }.reject! {|_,v| v.nil? }
380
+ #
381
+ # prs:
382
+ # links
383
+ # [x] https://github.com/rails-api/active_model_serializers/pull/1454
384
+ # meta
385
+ # [x] https://github.com/rails-api/active_model_serializers/pull/1454
386
+ # polymorphic
387
+ # [ ] https://github.com/rails-api/active_model_serializers/pull/1420
388
+ #
389
+ # relationshipsData
390
+ # definition:
391
+ # oneOf
392
+ # relationshipToOne
393
+ # relationshipToMany
394
+ #
395
+ # description:
396
+ # Member, whose value represents "resource linkage"
397
+ # structure:
398
+ # if has_one?
399
+ # relationshipToOne
400
+ # else
401
+ # relationshipToMany
402
+ # end
403
+ #
404
+ # definition:
405
+ # anyOf
406
+ # null
407
+ # linkage
408
+ #
409
+ # relationshipToOne
410
+ # description:
411
+ #
412
+ # References to other resource objects in a to-one ("relationship"). Relationships can be
413
+ # specified by including a member in a resource's links object.
414
+ #
415
+ # None: Describes an empty to-one relationship.
416
+ # structure:
417
+ # if has_related?
418
+ # linkage
419
+ # else
420
+ # nil
421
+ # end
422
+ #
423
+ # relationshipToMany
424
+ # definition:
425
+ # array of unique items of type 'linkage'
426
+ #
427
+ # description:
428
+ # An array of objects each containing "type" and "id" members for to-many relationships
429
+ # structure:
430
+ # [
431
+ # linkage,
432
+ # linkage
433
+ # ]
434
+ # prs:
435
+ # polymorphic
436
+ # [ ] https://github.com/rails-api/active_model_serializers/pull/1282
437
+ #
438
+ # linkage
439
+ # definition:
440
+ # type (required) : String
441
+ # id (required) : String
442
+ # meta
443
+ #
444
+ # description:
445
+ # The "type" and "id" to non-empty members.
446
+ # structure:
447
+ # {
448
+ # type: 'required-type',
449
+ # id: 'required-id',
450
+ # meta: meta
451
+ # }.reject! {|_,v| v.nil? }
452
+ def relationships_for(serializer, requested_associations, include_slice)
453
+ include_directive = JSONAPI::IncludeDirective.new(
454
+ requested_associations,
455
+ allow_wildcard: true
456
+ )
457
+ serializer.associations(include_directive, include_slice).each_with_object({}) do |association, hash|
458
+ hash[association.key] = Relationship.new(serializer, instance_options, association).as_json
459
+ end
460
+ end
461
+
462
+ # {http://jsonapi.org/format/#document-links Document Links}
463
+ # links
464
+ # definition:
465
+ # JSON Object
466
+ #
467
+ # properties:
468
+ # self : URI
469
+ # related : link
470
+ #
471
+ # description:
472
+ # A resource object **MAY** contain references to other resource objects ("relationships").
473
+ # Relationships may be to-one or to-many. Relationships can be specified by including a member
474
+ # in a resource's links object.
475
+ #
476
+ # A `self` member’s value is a URL for the relationship itself (a "relationship URL"). This
477
+ # URL allows the client to directly manipulate the relationship. For example, it would allow
478
+ # a client to remove an `author` from an `article` without deleting the people resource
479
+ # itself.
480
+ # structure:
481
+ # {
482
+ # self: 'http://example.com/etc',
483
+ # related: link
484
+ # }.reject! {|_,v| v.nil? }
485
+ def links_for(serializer)
486
+ serializer._links.each_with_object({}) do |(name, value), hash|
487
+ next if value.excluded?(serializer)
488
+ result = Link.new(serializer, value.block).as_json
489
+ hash[name] = result if result
490
+ end
491
+ end
492
+
493
+ # {http://jsonapi.org/format/#fetching-pagination Pagination Links}
494
+ # pagination
495
+ # definition:
496
+ # first : pageObject
497
+ # last : pageObject
498
+ # prev : pageObject
499
+ # next : pageObject
500
+ # structure:
501
+ # {
502
+ # first: pageObject,
503
+ # last: pageObject,
504
+ # prev: pageObject,
505
+ # next: pageObject
506
+ # }
507
+ #
508
+ # pageObject
509
+ # definition:
510
+ # oneOf
511
+ # URI
512
+ # null
513
+ #
514
+ # description:
515
+ # The <x> page of data
516
+ # structure:
517
+ # if has_page?
518
+ # 'http://example.com/some-page?page[number][x]'
519
+ # else
520
+ # nil
521
+ # end
522
+ # prs:
523
+ # https://github.com/rails-api/active_model_serializers/pull/1041
524
+ def pagination_links_for(serializer)
525
+ PaginationLinks.new(serializer.object, instance_options).as_json
526
+ end
527
+
528
+ # {http://jsonapi.org/format/#document-meta Docment Meta}
529
+ def meta_for(serializer)
530
+ Meta.new(serializer).as_json
531
+ end
532
+ end
533
+ end
534
+ end
535
+ # rubocop:enable Style/AsciiComments
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModelSerializers
4
+ module Adapter
5
+ class Null < Base
6
+ def serializable_hash(*)
7
+ {}
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModelSerializers
4
+ module Adapter
5
+ UnknownAdapterError = Class.new(ArgumentError)
6
+ ADAPTER_MAP = {} # rubocop:disable Style/MutableConstant
7
+ private_constant :ADAPTER_MAP if defined?(private_constant)
8
+
9
+ class << self # All methods are class functions
10
+ # :nocov:
11
+ def new(*args)
12
+ fail ArgumentError, 'Adapters inherit from Adapter::Base.' \
13
+ "Adapter.new called with args: '#{args.inspect}', from" \
14
+ "'caller[0]'."
15
+ end
16
+ # :nocov:
17
+
18
+ def configured_adapter
19
+ lookup(ActiveModelSerializers.config.adapter)
20
+ end
21
+
22
+ def create(resource, options = {})
23
+ override = options.delete(:adapter)
24
+ klass = override ? adapter_class(override) : configured_adapter
25
+ klass.new(resource, options)
26
+ end
27
+
28
+ # @see ActiveModelSerializers::Adapter.lookup
29
+ def adapter_class(adapter)
30
+ ActiveModelSerializers::Adapter.lookup(adapter)
31
+ end
32
+
33
+ # @return [Hash<adapter_name, adapter_class>]
34
+ def adapter_map
35
+ ADAPTER_MAP
36
+ end
37
+
38
+ # @return [Array<Symbol>] list of adapter names
39
+ def adapters
40
+ adapter_map.keys.sort!
41
+ end
42
+
43
+ # Adds an adapter 'klass' with 'name' to the 'adapter_map'
44
+ # Names are stringified and underscored
45
+ # @param name [Symbol, String, Class] name of the registered adapter
46
+ # @param klass [Class] adapter class itself, optional if name is the class
47
+ # @example
48
+ # AMS::Adapter.register(:my_adapter, MyAdapter)
49
+ # @note The registered name strips out 'ActiveModelSerializers::Adapter::'
50
+ # so that registering 'ActiveModelSerializers::Adapter::Json' and
51
+ # 'Json' will both register as 'json'.
52
+ def register(name, klass = name)
53
+ name = name.to_s.gsub(/\AActiveModelSerializers::Adapter::/, ''.freeze)
54
+ adapter_map[name.underscore] = klass
55
+ self
56
+ end
57
+
58
+ def registered_name(adapter_class)
59
+ ADAPTER_MAP.key adapter_class
60
+ end
61
+
62
+ # @param adapter [String, Symbol, Class] name to fetch adapter by
63
+ # @return [ActiveModelSerializers::Adapter] subclass of Adapter
64
+ # @raise [UnknownAdapterError]
65
+ def lookup(adapter)
66
+ # 1. return if is a class
67
+ return adapter if adapter.is_a?(Class)
68
+ adapter_name = adapter.to_s.underscore
69
+ # 2. return if registered
70
+ adapter_map.fetch(adapter_name) do
71
+ # 3. try to find adapter class from environment
72
+ adapter_class = find_by_name(adapter_name)
73
+ register(adapter_name, adapter_class)
74
+ adapter_class
75
+ end
76
+ rescue NameError, ArgumentError => e
77
+ failure_message =
78
+ "NameError: #{e.message}. Unknown adapter: #{adapter.inspect}. Valid adapters are: #{adapters}"
79
+ raise UnknownAdapterError, failure_message, e.backtrace
80
+ end
81
+
82
+ # @api private
83
+ def find_by_name(adapter_name)
84
+ adapter_name = adapter_name.to_s.classify.tr('API', 'Api')
85
+ "ActiveModelSerializers::Adapter::#{adapter_name}".safe_constantize ||
86
+ "ActiveModelSerializers::Adapter::#{adapter_name.pluralize}".safe_constantize or # rubocop:disable Style/AndOr
87
+ fail UnknownAdapterError
88
+ end
89
+ private :find_by_name
90
+ end
91
+
92
+ # Gotta be at the bottom to use the code above it :(
93
+ extend ActiveSupport::Autoload
94
+ autoload :Base
95
+ autoload :Null
96
+ autoload :Attributes
97
+ autoload :Json
98
+ autoload :JsonApi
99
+ end
100
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Adapted from
4
+ # https://github.com/rails/rails/blob/7f18ea14c8/activejob/lib/active_job/callbacks.rb
5
+ require 'active_support/callbacks'
6
+
7
+ module ActiveModelSerializers
8
+ # = ActiveModelSerializers Callbacks
9
+ #
10
+ # ActiveModelSerializers provides hooks during the life cycle of serialization and
11
+ # allow you to trigger logic. Available callbacks are:
12
+ #
13
+ # * <tt>around_render</tt>
14
+ #
15
+ module Callbacks
16
+ extend ActiveSupport::Concern
17
+ include ActiveSupport::Callbacks
18
+
19
+ included do
20
+ define_callbacks :render
21
+ end
22
+
23
+ # These methods will be included into any ActiveModelSerializers object, adding
24
+ # callbacks for +render+.
25
+ module ClassMethods
26
+ # Defines a callback that will get called around the render method,
27
+ # whether it is as_json, to_json, or serializable_hash
28
+ #
29
+ # class ActiveModelSerializers::SerializableResource
30
+ # include ActiveModelSerializers::Callbacks
31
+ #
32
+ # around_render do |args, block|
33
+ # tag_logger do
34
+ # notify_render do
35
+ # block.call(args)
36
+ # end
37
+ # end
38
+ # end
39
+ #
40
+ # def as_json
41
+ # run_callbacks :render do
42
+ # adapter.as_json
43
+ # end
44
+ # end
45
+ # # Note: So that we can re-use the instrumenter for as_json, to_json, and
46
+ # # serializable_hash, we aren't using the usual format, which would be:
47
+ # # def render(args)
48
+ # # adapter.as_json
49
+ # # end
50
+ # end
51
+ #
52
+ def around_render(*filters, &blk)
53
+ set_callback(:render, :around, *filters, &blk)
54
+ end
55
+ end
56
+ end
57
+ end