active_model_serializers 0.8.3 → 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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +726 -6
- data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
- data/README.md +194 -545
- data/lib/action_controller/serialization.rb +53 -38
- data/lib/active_model/serializable_resource.rb +13 -0
- data/lib/active_model/serializer/adapter/attributes.rb +17 -0
- data/lib/active_model/serializer/adapter/base.rb +20 -0
- data/lib/active_model/serializer/adapter/json.rb +17 -0
- data/lib/active_model/serializer/adapter/json_api.rb +17 -0
- data/lib/active_model/serializer/adapter/null.rb +17 -0
- data/lib/active_model/serializer/adapter.rb +26 -0
- data/lib/active_model/serializer/array_serializer.rb +14 -0
- data/lib/active_model/serializer/association.rb +73 -0
- data/lib/active_model/serializer/attribute.rb +27 -0
- data/lib/active_model/serializer/belongs_to_reflection.rb +13 -0
- data/lib/active_model/serializer/collection_serializer.rb +99 -0
- data/lib/active_model/serializer/concerns/caching.rb +305 -0
- data/lib/active_model/serializer/error_serializer.rb +16 -0
- data/lib/active_model/serializer/errors_serializer.rb +34 -0
- data/lib/active_model/serializer/field.rb +92 -0
- data/lib/active_model/serializer/fieldset.rb +33 -0
- data/lib/active_model/serializer/has_many_reflection.rb +12 -0
- data/lib/active_model/serializer/has_one_reflection.rb +9 -0
- data/lib/active_model/serializer/lazy_association.rb +99 -0
- data/lib/active_model/serializer/link.rb +23 -0
- data/lib/active_model/serializer/lint.rb +152 -0
- data/lib/active_model/serializer/null.rb +19 -0
- data/lib/active_model/serializer/reflection.rb +212 -0
- data/lib/active_model/serializer/version.rb +7 -0
- data/lib/active_model/serializer.rb +354 -442
- data/lib/active_model_serializers/adapter/attributes.rb +36 -0
- data/lib/active_model_serializers/adapter/base.rb +85 -0
- data/lib/active_model_serializers/adapter/json.rb +23 -0
- data/lib/active_model_serializers/adapter/json_api/deserialization.rb +215 -0
- data/lib/active_model_serializers/adapter/json_api/error.rb +98 -0
- data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +51 -0
- data/lib/active_model_serializers/adapter/json_api/link.rb +85 -0
- data/lib/active_model_serializers/adapter/json_api/meta.rb +39 -0
- data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +94 -0
- data/lib/active_model_serializers/adapter/json_api/relationship.rb +106 -0
- data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +68 -0
- data/lib/active_model_serializers/adapter/json_api.rb +535 -0
- data/lib/active_model_serializers/adapter/null.rb +11 -0
- data/lib/active_model_serializers/adapter.rb +100 -0
- data/lib/active_model_serializers/callbacks.rb +57 -0
- data/lib/active_model_serializers/deprecate.rb +56 -0
- data/lib/active_model_serializers/deserialization.rb +17 -0
- data/lib/active_model_serializers/json_pointer.rb +16 -0
- data/lib/active_model_serializers/logging.rb +124 -0
- data/lib/active_model_serializers/lookup_chain.rb +82 -0
- data/lib/active_model_serializers/model.rb +132 -0
- data/lib/active_model_serializers/railtie.rb +62 -0
- data/lib/active_model_serializers/register_jsonapi_renderer.rb +80 -0
- data/lib/active_model_serializers/serializable_resource.rb +84 -0
- data/lib/active_model_serializers/serialization_context.rb +41 -0
- data/lib/active_model_serializers/test/schema.rb +140 -0
- data/lib/active_model_serializers/test/serializer.rb +127 -0
- data/lib/active_model_serializers/test.rb +9 -0
- data/lib/active_model_serializers.rb +49 -81
- data/lib/generators/rails/USAGE +6 -0
- data/lib/generators/rails/resource_override.rb +12 -0
- data/lib/generators/rails/serializer_generator.rb +38 -0
- data/lib/generators/rails/templates/serializer.rb.erb +8 -0
- data/lib/grape/active_model_serializers.rb +18 -0
- data/lib/grape/formatters/active_model_serializers.rb +34 -0
- data/lib/grape/helpers/active_model_serializers.rb +19 -0
- data/lib/tasks/rubocop.rake +60 -0
- metadata +240 -51
- data/.gitignore +0 -18
- data/.travis.yml +0 -28
- data/DESIGN.textile +0 -586
- data/Gemfile +0 -4
- data/Gemfile.edge +0 -9
- data/Rakefile +0 -18
- data/active_model_serializers.gemspec +0 -24
- data/bench/perf.rb +0 -43
- data/cruft.md +0 -19
- data/lib/active_model/array_serializer.rb +0 -104
- data/lib/active_model/serializer/associations.rb +0 -233
- data/lib/active_model/serializers/version.rb +0 -5
- data/lib/active_record/serializer_override.rb +0 -16
- data/lib/generators/resource_override.rb +0 -13
- data/lib/generators/serializer/USAGE +0 -9
- data/lib/generators/serializer/serializer_generator.rb +0 -42
- data/lib/generators/serializer/templates/serializer.rb +0 -19
- data/test/array_serializer_test.rb +0 -75
- data/test/association_test.rb +0 -592
- data/test/caching_test.rb +0 -96
- data/test/generators_test.rb +0 -85
- data/test/no_serialization_scope_test.rb +0 -34
- data/test/serialization_scope_name_test.rb +0 -67
- data/test/serialization_test.rb +0 -392
- data/test/serializer_support_test.rb +0 -51
- data/test/serializer_test.rb +0 -1465
- data/test/test_fakes.rb +0 -217
- 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,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
|