active_model_serializers 0.9.12 → 0.10.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +638 -82
  3. data/MIT-LICENSE +3 -2
  4. data/README.md +194 -846
  5. data/lib/action_controller/serialization.rb +34 -74
  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 +53 -38
  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 +1 -1
  31. data/lib/active_model/serializer.rb +361 -263
  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.rb +132 -0
  53. data/lib/active_model_serializers/railtie.rb +62 -0
  54. data/lib/active_model_serializers/register_jsonapi_renderer.rb +80 -0
  55. data/lib/active_model_serializers/serializable_resource.rb +84 -0
  56. data/lib/active_model_serializers/serialization_context.rb +41 -0
  57. data/lib/active_model_serializers/test/schema.rb +140 -0
  58. data/lib/active_model_serializers/test/serializer.rb +127 -0
  59. data/lib/active_model_serializers/test.rb +9 -0
  60. data/lib/active_model_serializers.rb +58 -27
  61. data/lib/generators/rails/USAGE +6 -0
  62. data/lib/{active_model/serializer/generators → generators/rails}/resource_override.rb +1 -4
  63. data/lib/{active_model/serializer/generators/serializer → generators/rails}/serializer_generator.rb +4 -5
  64. data/lib/grape/active_model_serializers.rb +18 -0
  65. data/lib/grape/formatters/active_model_serializers.rb +34 -0
  66. data/lib/grape/helpers/active_model_serializers.rb +19 -0
  67. data/lib/tasks/rubocop.rake +60 -0
  68. metadata +248 -155
  69. data/CONTRIBUTING.md +0 -20
  70. data/DESIGN.textile +0 -586
  71. data/lib/action_controller/serialization_test_case.rb +0 -82
  72. data/lib/active_model/array_serializer.rb +0 -70
  73. data/lib/active_model/default_serializer.rb +0 -30
  74. data/lib/active_model/serializable/utils.rb +0 -18
  75. data/lib/active_model/serializable.rb +0 -61
  76. data/lib/active_model/serializer/association/has_many.rb +0 -41
  77. data/lib/active_model/serializer/association/has_one.rb +0 -27
  78. data/lib/active_model/serializer/config.rb +0 -33
  79. data/lib/active_model/serializer/generators/serializer/USAGE +0 -9
  80. data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +0 -16
  81. data/lib/active_model/serializer/generators/serializer/templates/controller.rb +0 -93
  82. data/lib/active_model/serializer/railtie.rb +0 -24
  83. data/lib/active_model/serializer_support.rb +0 -7
  84. data/test/benchmark/app.rb +0 -60
  85. data/test/benchmark/benchmarking_support.rb +0 -67
  86. data/test/benchmark/bm_active_record.rb +0 -41
  87. data/test/benchmark/setup.rb +0 -75
  88. data/test/benchmark/tmp/miniprofiler/mp_timers_6eqewtfgrhitvq5gqm25 +0 -0
  89. data/test/benchmark/tmp/miniprofiler/mp_timers_8083sx03hu72pxz1a4d0 +0 -0
  90. data/test/benchmark/tmp/miniprofiler/mp_timers_fyz2gsml4z0ph9kpoy1c +0 -0
  91. data/test/benchmark/tmp/miniprofiler/mp_timers_hjry5rc32imd42oxoi48 +0 -0
  92. data/test/benchmark/tmp/miniprofiler/mp_timers_m8fpoz2cvt3g9agz0bs3 +0 -0
  93. data/test/benchmark/tmp/miniprofiler/mp_timers_p92m2drnj1i568u3sta0 +0 -0
  94. data/test/benchmark/tmp/miniprofiler/mp_timers_qg52tpca3uesdfguee9i +0 -0
  95. data/test/benchmark/tmp/miniprofiler/mp_timers_s15t1a6mvxe0z7vjv790 +0 -0
  96. data/test/benchmark/tmp/miniprofiler/mp_timers_x8kal3d17nfds6vp4kcj +0 -0
  97. data/test/benchmark/tmp/miniprofiler/mp_views_127.0.0.1 +0 -0
  98. data/test/fixtures/active_record.rb +0 -96
  99. data/test/fixtures/poro.rb +0 -255
  100. data/test/fixtures/template.html.erb +0 -1
  101. data/test/integration/action_controller/namespaced_serialization_test.rb +0 -105
  102. data/test/integration/action_controller/serialization_test.rb +0 -287
  103. data/test/integration/action_controller/serialization_test_case_test.rb +0 -71
  104. data/test/integration/active_record/active_record_test.rb +0 -94
  105. data/test/integration/generators/resource_generator_test.rb +0 -26
  106. data/test/integration/generators/scaffold_controller_generator_test.rb +0 -64
  107. data/test/integration/generators/serializer_generator_test.rb +0 -41
  108. data/test/test_app.rb +0 -18
  109. data/test/test_helper.rb +0 -31
  110. data/test/tmp/app/assets/javascripts/accounts.js +0 -2
  111. data/test/tmp/app/assets/stylesheets/accounts.css +0 -4
  112. data/test/tmp/app/controllers/accounts_controller.rb +0 -3
  113. data/test/tmp/app/helpers/accounts_helper.rb +0 -3
  114. data/test/tmp/app/serializers/account_serializer.rb +0 -4
  115. data/test/tmp/config/routes.rb +0 -2
  116. data/test/unit/active_model/array_serializer/except_test.rb +0 -18
  117. data/test/unit/active_model/array_serializer/key_format_test.rb +0 -18
  118. data/test/unit/active_model/array_serializer/meta_test.rb +0 -53
  119. data/test/unit/active_model/array_serializer/only_test.rb +0 -18
  120. data/test/unit/active_model/array_serializer/options_test.rb +0 -16
  121. data/test/unit/active_model/array_serializer/root_test.rb +0 -102
  122. data/test/unit/active_model/array_serializer/scope_test.rb +0 -24
  123. data/test/unit/active_model/array_serializer/serialization_test.rb +0 -239
  124. data/test/unit/active_model/default_serializer_test.rb +0 -13
  125. data/test/unit/active_model/serializer/associations/build_serializer_test.rb +0 -36
  126. data/test/unit/active_model/serializer/associations_test.rb +0 -49
  127. data/test/unit/active_model/serializer/attributes_test.rb +0 -57
  128. data/test/unit/active_model/serializer/config_test.rb +0 -91
  129. data/test/unit/active_model/serializer/filter_test.rb +0 -69
  130. data/test/unit/active_model/serializer/has_many_polymorphic_test.rb +0 -189
  131. data/test/unit/active_model/serializer/has_many_test.rb +0 -265
  132. data/test/unit/active_model/serializer/has_one_and_has_many_test.rb +0 -27
  133. data/test/unit/active_model/serializer/has_one_polymorphic_test.rb +0 -196
  134. data/test/unit/active_model/serializer/has_one_test.rb +0 -253
  135. data/test/unit/active_model/serializer/key_format_test.rb +0 -25
  136. data/test/unit/active_model/serializer/meta_test.rb +0 -39
  137. data/test/unit/active_model/serializer/options_test.rb +0 -42
  138. data/test/unit/active_model/serializer/root_test.rb +0 -117
  139. data/test/unit/active_model/serializer/scope_test.rb +0 -49
  140. data/test/unit/active_model/serializer/url_helpers_test.rb +0 -36
  141. data/test/unit/active_model/serilizable_test.rb +0 -50
  142. /data/lib/{active_model/serializer/generators/serializer/templates/serializer.rb → generators/rails/templates/serializer.rb.erb} +0 -0
@@ -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