caprese 0.2.0
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 +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +12 -0
- data/.travis.yml +18 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/caprese.gemspec +38 -0
- data/lib/caprese/adapter/json_api/error.rb +123 -0
- data/lib/caprese/adapter/json_api/json_pointer.rb +52 -0
- data/lib/caprese/adapter/json_api/jsonapi.rb +49 -0
- data/lib/caprese/adapter/json_api/link.rb +88 -0
- data/lib/caprese/adapter/json_api/meta.rb +37 -0
- data/lib/caprese/adapter/json_api/pagination_links.rb +69 -0
- data/lib/caprese/adapter/json_api/relationship.rb +65 -0
- data/lib/caprese/adapter/json_api/resource_identifier.rb +49 -0
- data/lib/caprese/adapter/json_api.rb +509 -0
- data/lib/caprese/concerns/versioning.rb +69 -0
- data/lib/caprese/controller/concerns/callbacks.rb +60 -0
- data/lib/caprese/controller/concerns/errors.rb +42 -0
- data/lib/caprese/controller/concerns/persistence.rb +327 -0
- data/lib/caprese/controller/concerns/query.rb +209 -0
- data/lib/caprese/controller/concerns/relationships.rb +250 -0
- data/lib/caprese/controller/concerns/rendering.rb +43 -0
- data/lib/caprese/controller/concerns/typing.rb +39 -0
- data/lib/caprese/controller.rb +26 -0
- data/lib/caprese/error.rb +121 -0
- data/lib/caprese/errors.rb +69 -0
- data/lib/caprese/record/errors.rb +82 -0
- data/lib/caprese/record.rb +19 -0
- data/lib/caprese/routing/caprese_resources.rb +27 -0
- data/lib/caprese/serializer/concerns/links.rb +96 -0
- data/lib/caprese/serializer/concerns/lookup.rb +37 -0
- data/lib/caprese/serializer/error_serializer.rb +13 -0
- data/lib/caprese/serializer.rb +13 -0
- data/lib/caprese/version.rb +3 -0
- data/lib/caprese.rb +35 -0
- metadata +273 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module Caprese
|
2
|
+
module Adapter
|
3
|
+
class JsonApi
|
4
|
+
class ResourceIdentifier
|
5
|
+
def self.type_for(class_name, serializer_type = nil, transform_options = {})
|
6
|
+
if serializer_type
|
7
|
+
raw_type = serializer_type
|
8
|
+
else
|
9
|
+
inflection =
|
10
|
+
if ActiveModelSerializers.config.jsonapi_resource_type == :singular
|
11
|
+
:singularize
|
12
|
+
else
|
13
|
+
:pluralize
|
14
|
+
end
|
15
|
+
|
16
|
+
raw_type = class_name.underscore
|
17
|
+
raw_type = ActiveSupport::Inflector.public_send(inflection, raw_type)
|
18
|
+
raw_type
|
19
|
+
end
|
20
|
+
JsonApi.send(:transform_key_casing!, raw_type, transform_options)
|
21
|
+
end
|
22
|
+
|
23
|
+
# {http://jsonapi.org/format/#document-resource-identifier-objects Resource Identifier Objects}
|
24
|
+
def initialize(serializer, options)
|
25
|
+
@id = id_for(serializer)
|
26
|
+
@type = type_for(serializer, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def as_json
|
30
|
+
{ id: id, type: type }
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
attr_reader :id, :type
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def type_for(serializer, transform_options)
|
40
|
+
self.class.type_for(serializer.object.class.name, serializer._type, transform_options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def id_for(serializer)
|
44
|
+
serializer.read_attribute_for_serialization(Caprese.config.resource_primary_key).to_s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,509 @@
|
|
1
|
+
require 'active_model_serializers'
|
2
|
+
|
3
|
+
module Caprese
|
4
|
+
module Adapter
|
5
|
+
class JsonApi < ActiveModelSerializers::Adapter::Base
|
6
|
+
extend ActiveSupport::Autoload
|
7
|
+
autoload :Jsonapi
|
8
|
+
autoload :ResourceIdentifier
|
9
|
+
autoload :Relationship
|
10
|
+
autoload :Link
|
11
|
+
autoload :PaginationLinks
|
12
|
+
autoload :Meta
|
13
|
+
autoload :Error
|
14
|
+
autoload :Deserialization
|
15
|
+
|
16
|
+
def self.default_key_transform
|
17
|
+
:dash
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.fragment_cache(cached_hash, non_cached_hash, root = true)
|
21
|
+
core_cached = cached_hash.first
|
22
|
+
core_non_cached = non_cached_hash.first
|
23
|
+
no_root_cache = cached_hash.delete_if { |key, _value| key == core_cached[0] }
|
24
|
+
no_root_non_cache = non_cached_hash.delete_if { |key, _value| key == core_non_cached[0] }
|
25
|
+
cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
|
26
|
+
hash = root ? { root => cached_resource } : cached_resource
|
27
|
+
|
28
|
+
hash.deep_merge no_root_non_cache.deep_merge no_root_cache
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(serializer, options = {})
|
32
|
+
super
|
33
|
+
@include_directive = JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
|
34
|
+
@fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
|
35
|
+
end
|
36
|
+
|
37
|
+
# {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure}
|
38
|
+
# {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.}
|
39
|
+
def serializable_hash(*)
|
40
|
+
document = if serializer.success?
|
41
|
+
success_document
|
42
|
+
else
|
43
|
+
failure_document
|
44
|
+
end
|
45
|
+
self.class.transform_key_casing!(document, instance_options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def fragment_cache(cached_hash, non_cached_hash)
|
49
|
+
root = !instance_options.include?(:include)
|
50
|
+
self.class.fragment_cache(cached_hash, non_cached_hash, root)
|
51
|
+
end
|
52
|
+
|
53
|
+
# {http://jsonapi.org/format/#document-top-level Primary data}
|
54
|
+
# definition:
|
55
|
+
# ☐ toplevel_data (required)
|
56
|
+
# ☐ toplevel_included
|
57
|
+
# ☑ toplevel_meta
|
58
|
+
# ☑ toplevel_links
|
59
|
+
# ☑ toplevel_jsonapi
|
60
|
+
# structure:
|
61
|
+
# {
|
62
|
+
# data: toplevel_data,
|
63
|
+
# included: toplevel_included,
|
64
|
+
# meta: toplevel_meta,
|
65
|
+
# links: toplevel_links,
|
66
|
+
# jsonapi: toplevel_jsonapi
|
67
|
+
# }.reject! {|_,v| v.nil? }
|
68
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
69
|
+
def success_document
|
70
|
+
is_collection = serializer.respond_to?(:each)
|
71
|
+
serializers = is_collection ? serializer : [serializer]
|
72
|
+
primary_data, included = resource_objects_for(serializers)
|
73
|
+
|
74
|
+
hash = {}
|
75
|
+
# toplevel_data
|
76
|
+
# definition:
|
77
|
+
# oneOf
|
78
|
+
# resource
|
79
|
+
# array of unique items of type 'resource'
|
80
|
+
# null
|
81
|
+
#
|
82
|
+
# description:
|
83
|
+
# The document's "primary data" is a representation of the resource or collection of resources
|
84
|
+
# targeted by a request.
|
85
|
+
#
|
86
|
+
# Singular: the resource object.
|
87
|
+
#
|
88
|
+
# Collection: one of an array of resource objects, an array of resource identifier objects, or
|
89
|
+
# an empty array ([]), for requests that target resource collections.
|
90
|
+
#
|
91
|
+
# None: null if the request is one that might correspond to a single resource, but doesn't currently.
|
92
|
+
# structure:
|
93
|
+
# if serializable_resource.resource?
|
94
|
+
# resource
|
95
|
+
# elsif serializable_resource.collection?
|
96
|
+
# [
|
97
|
+
# resource,
|
98
|
+
# resource
|
99
|
+
# ]
|
100
|
+
# else
|
101
|
+
# nil
|
102
|
+
# end
|
103
|
+
hash[:data] = is_collection ? primary_data : primary_data[0]
|
104
|
+
# toplevel_included
|
105
|
+
# alias included
|
106
|
+
# definition:
|
107
|
+
# array of unique items of type 'resource'
|
108
|
+
#
|
109
|
+
# description:
|
110
|
+
# To reduce the number of HTTP requests, servers **MAY** allow
|
111
|
+
# responses that include related resources along with the requested primary
|
112
|
+
# resources. Such responses are called "compound documents".
|
113
|
+
# structure:
|
114
|
+
# [
|
115
|
+
# resource,
|
116
|
+
# resource
|
117
|
+
# ]
|
118
|
+
hash[:included] = included if included.any?
|
119
|
+
|
120
|
+
Jsonapi.add!(hash)
|
121
|
+
|
122
|
+
if instance_options[:links]
|
123
|
+
hash[:links] ||= {}
|
124
|
+
hash[:links].update(instance_options[:links])
|
125
|
+
end
|
126
|
+
|
127
|
+
if is_collection && serializer.paginated?
|
128
|
+
hash[:links] ||= {}
|
129
|
+
hash[:links].update(pagination_links_for(serializer))
|
130
|
+
end
|
131
|
+
|
132
|
+
hash[:meta] = instance_options[:meta] unless instance_options[:meta].blank?
|
133
|
+
|
134
|
+
hash
|
135
|
+
end
|
136
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
137
|
+
|
138
|
+
# {http://jsonapi.org/format/#errors JSON API Errors}
|
139
|
+
# TODO: look into caching
|
140
|
+
# definition:
|
141
|
+
# ☑ toplevel_errors array (required)
|
142
|
+
# ☐ toplevel_meta
|
143
|
+
# ☐ toplevel_jsonapi
|
144
|
+
# structure:
|
145
|
+
# {
|
146
|
+
# errors: toplevel_errors,
|
147
|
+
# meta: toplevel_meta,
|
148
|
+
# jsonapi: toplevel_jsonapi
|
149
|
+
# }.reject! {|_,v| v.nil? }
|
150
|
+
# prs:
|
151
|
+
# https://github.com/rails-api/active_model_serializers/pull/1004
|
152
|
+
def failure_document
|
153
|
+
hash = {}
|
154
|
+
# PR Please :)
|
155
|
+
# Jsonapi.add!(hash)
|
156
|
+
|
157
|
+
# toplevel_errors
|
158
|
+
# definition:
|
159
|
+
# array of unique items of type 'error'
|
160
|
+
# structure:
|
161
|
+
# [
|
162
|
+
# error,
|
163
|
+
# error
|
164
|
+
# ]
|
165
|
+
if serializer.respond_to?(:each)
|
166
|
+
hash[:errors] = serializer.flat_map do |error_serializer|
|
167
|
+
Error.resource_errors(error_serializer, instance_options)
|
168
|
+
end
|
169
|
+
else
|
170
|
+
hash[:errors] =
|
171
|
+
if serializer.resource_errors?
|
172
|
+
Error.resource_errors(serializer, instance_options)
|
173
|
+
else
|
174
|
+
Error.param_errors(serializer, instance_options)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
hash
|
178
|
+
end
|
179
|
+
|
180
|
+
protected
|
181
|
+
|
182
|
+
attr_reader :fieldset
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
# {http://jsonapi.org/format/#document-resource-objects Primary data}
|
187
|
+
# resource
|
188
|
+
# definition:
|
189
|
+
# JSON Object
|
190
|
+
#
|
191
|
+
# properties:
|
192
|
+
# type (required) : String
|
193
|
+
# id (required) : String
|
194
|
+
# attributes
|
195
|
+
# relationships
|
196
|
+
# links
|
197
|
+
# meta
|
198
|
+
#
|
199
|
+
# description:
|
200
|
+
# "Resource objects" appear in a JSON API document to represent resources
|
201
|
+
# structure:
|
202
|
+
# {
|
203
|
+
# type: 'admin--some-user',
|
204
|
+
# id: '1336',
|
205
|
+
# attributes: attributes,
|
206
|
+
# relationships: relationships,
|
207
|
+
# links: links,
|
208
|
+
# meta: meta,
|
209
|
+
# }.reject! {|_,v| v.nil? }
|
210
|
+
# prs:
|
211
|
+
# type
|
212
|
+
# https://github.com/rails-api/active_model_serializers/pull/1122
|
213
|
+
# [x] https://github.com/rails-api/active_model_serializers/pull/1213
|
214
|
+
# https://github.com/rails-api/active_model_serializers/pull/1216
|
215
|
+
# https://github.com/rails-api/active_model_serializers/pull/1029
|
216
|
+
# links
|
217
|
+
# [x] https://github.com/rails-api/active_model_serializers/pull/1246
|
218
|
+
# [x] url helpers https://github.com/rails-api/active_model_serializers/issues/1269
|
219
|
+
# meta
|
220
|
+
# [x] https://github.com/rails-api/active_model_serializers/pull/1340
|
221
|
+
def resource_objects_for(serializers)
|
222
|
+
@primary = []
|
223
|
+
@included = []
|
224
|
+
@resource_identifiers = Set.new
|
225
|
+
serializers.each { |serializer| process_resource(serializer, true, @include_directive) }
|
226
|
+
serializers.each { |serializer| process_relationships(serializer, @include_directive) }
|
227
|
+
|
228
|
+
[@primary, @included]
|
229
|
+
end
|
230
|
+
|
231
|
+
def process_resource(serializer, primary, included_associations)
|
232
|
+
resource_identifier = ResourceIdentifier.new(serializer, instance_options).as_json
|
233
|
+
return false unless @resource_identifiers.add?(resource_identifier)
|
234
|
+
|
235
|
+
resource_object = resource_object_for(serializer, included_associations)
|
236
|
+
if primary
|
237
|
+
@primary << resource_object
|
238
|
+
else
|
239
|
+
@included << resource_object
|
240
|
+
end
|
241
|
+
|
242
|
+
true
|
243
|
+
end
|
244
|
+
|
245
|
+
def process_relationships(serializer, include_directive)
|
246
|
+
serializer.associations(include_directive).each do |association|
|
247
|
+
process_relationship(association.serializer, include_directive[association.key])
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def process_relationship(serializer, include_directive)
|
252
|
+
if serializer.respond_to?(:each)
|
253
|
+
serializer.each { |s| process_relationship(s, include_directive) }
|
254
|
+
return
|
255
|
+
end
|
256
|
+
return unless serializer && serializer.object
|
257
|
+
return unless process_resource(serializer, false, include_directive)
|
258
|
+
|
259
|
+
process_relationships(serializer, include_directive)
|
260
|
+
end
|
261
|
+
|
262
|
+
# {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes}
|
263
|
+
# attributes
|
264
|
+
# definition:
|
265
|
+
# JSON Object
|
266
|
+
#
|
267
|
+
# patternProperties:
|
268
|
+
# ^(?!relationships$|links$)\\w[-\\w_]*$
|
269
|
+
#
|
270
|
+
# description:
|
271
|
+
# Members of the attributes object ("attributes") represent information about the resource
|
272
|
+
# object in which it's defined.
|
273
|
+
# Attributes may contain any valid JSON value
|
274
|
+
# structure:
|
275
|
+
# {
|
276
|
+
# foo: 'bar'
|
277
|
+
# }
|
278
|
+
def attributes_for(serializer, fields)
|
279
|
+
serializer.attributes(fields).except(:id)
|
280
|
+
end
|
281
|
+
|
282
|
+
# {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
|
283
|
+
def resource_object_for(serializer, included_associations)
|
284
|
+
resource_object = serializer.fetch(self) do
|
285
|
+
resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
|
286
|
+
|
287
|
+
requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
|
288
|
+
attributes = attributes_for(serializer, requested_fields)
|
289
|
+
resource_object[:attributes] = attributes if attributes.any?
|
290
|
+
resource_object
|
291
|
+
end
|
292
|
+
|
293
|
+
requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
|
294
|
+
relationships = relationships_for(serializer, requested_associations, included_associations)
|
295
|
+
resource_object[:relationships] = relationships if relationships.any?
|
296
|
+
|
297
|
+
links = links_for(serializer)
|
298
|
+
# toplevel_links
|
299
|
+
# definition:
|
300
|
+
# allOf
|
301
|
+
# ☐ links
|
302
|
+
# ☐ pagination
|
303
|
+
#
|
304
|
+
# description:
|
305
|
+
# Link members related to the primary data.
|
306
|
+
# structure:
|
307
|
+
# links.merge!(pagination)
|
308
|
+
# prs:
|
309
|
+
# https://github.com/rails-api/active_model_serializers/pull/1247
|
310
|
+
# https://github.com/rails-api/active_model_serializers/pull/1018
|
311
|
+
resource_object[:links] = links if links.any?
|
312
|
+
|
313
|
+
# toplevel_meta
|
314
|
+
# alias meta
|
315
|
+
# definition:
|
316
|
+
# meta
|
317
|
+
# structure
|
318
|
+
# {
|
319
|
+
# :'git-ref' => 'abc123'
|
320
|
+
# }
|
321
|
+
meta = meta_for(serializer)
|
322
|
+
resource_object[:meta] = meta unless meta.blank?
|
323
|
+
|
324
|
+
resource_object
|
325
|
+
end
|
326
|
+
|
327
|
+
# {http://jsonapi.org/format/#document-resource-object-relationships Document Resource Object Relationship}
|
328
|
+
# relationships
|
329
|
+
# definition:
|
330
|
+
# JSON Object
|
331
|
+
#
|
332
|
+
# patternProperties:
|
333
|
+
# ^\\w[-\\w_]*$"
|
334
|
+
#
|
335
|
+
# properties:
|
336
|
+
# data : relationshipsData
|
337
|
+
# links
|
338
|
+
# meta
|
339
|
+
#
|
340
|
+
# description:
|
341
|
+
#
|
342
|
+
# Members of the relationships object ("relationships") represent references from the
|
343
|
+
# resource object in which it's defined to other resource objects."
|
344
|
+
# structure:
|
345
|
+
# {
|
346
|
+
# links: links,
|
347
|
+
# meta: meta,
|
348
|
+
# data: relationshipsData
|
349
|
+
# }.reject! {|_,v| v.nil? }
|
350
|
+
#
|
351
|
+
# prs:
|
352
|
+
# links
|
353
|
+
# [x] https://github.com/rails-api/active_model_serializers/pull/1454
|
354
|
+
# meta
|
355
|
+
# [x] https://github.com/rails-api/active_model_serializers/pull/1454
|
356
|
+
# polymorphic
|
357
|
+
# [ ] https://github.com/rails-api/active_model_serializers/pull/1420
|
358
|
+
#
|
359
|
+
# relationshipsData
|
360
|
+
# definition:
|
361
|
+
# oneOf
|
362
|
+
# relationshipToOne
|
363
|
+
# relationshipToMany
|
364
|
+
#
|
365
|
+
# description:
|
366
|
+
# Member, whose value represents "resource linkage"
|
367
|
+
# structure:
|
368
|
+
# if has_one?
|
369
|
+
# relationshipToOne
|
370
|
+
# else
|
371
|
+
# relationshipToMany
|
372
|
+
# end
|
373
|
+
#
|
374
|
+
# definition:
|
375
|
+
# anyOf
|
376
|
+
# null
|
377
|
+
# linkage
|
378
|
+
#
|
379
|
+
# relationshipToOne
|
380
|
+
# description:
|
381
|
+
#
|
382
|
+
# References to other resource objects in a to-one ("relationship"). Relationships can be
|
383
|
+
# specified by including a member in a resource's links object.
|
384
|
+
#
|
385
|
+
# None: Describes an empty to-one relationship.
|
386
|
+
# structure:
|
387
|
+
# if has_related?
|
388
|
+
# linkage
|
389
|
+
# else
|
390
|
+
# nil
|
391
|
+
# end
|
392
|
+
#
|
393
|
+
# relationshipToMany
|
394
|
+
# definition:
|
395
|
+
# array of unique items of type 'linkage'
|
396
|
+
#
|
397
|
+
# description:
|
398
|
+
# An array of objects each containing "type" and "id" members for to-many relationships
|
399
|
+
# structure:
|
400
|
+
# [
|
401
|
+
# linkage,
|
402
|
+
# linkage
|
403
|
+
# ]
|
404
|
+
# prs:
|
405
|
+
# polymorphic
|
406
|
+
# [ ] https://github.com/rails-api/active_model_serializers/pull/1282
|
407
|
+
#
|
408
|
+
# linkage
|
409
|
+
# definition:
|
410
|
+
# type (required) : String
|
411
|
+
# id (required) : String
|
412
|
+
# meta
|
413
|
+
#
|
414
|
+
# description:
|
415
|
+
# The "type" and "id" to non-empty members.
|
416
|
+
# structure:
|
417
|
+
# {
|
418
|
+
# type: 'required-type',
|
419
|
+
# id: 'required-id',
|
420
|
+
# meta: meta
|
421
|
+
# }.reject! {|_,v| v.nil? }
|
422
|
+
def relationships_for(serializer, requested_associations, included_associations)
|
423
|
+
include_directive = JSONAPI::IncludeDirective.new(
|
424
|
+
requested_associations,
|
425
|
+
allow_wildcard: true
|
426
|
+
)
|
427
|
+
serializer.associations(include_directive).each_with_object({}) do |association, hash|
|
428
|
+
hash[association.key] = Relationship.new(
|
429
|
+
serializer,
|
430
|
+
instance_options,
|
431
|
+
association,
|
432
|
+
included_associations
|
433
|
+
).as_json
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
# {http://jsonapi.org/format/#document-links Document Links}
|
438
|
+
# links
|
439
|
+
# definition:
|
440
|
+
# JSON Object
|
441
|
+
#
|
442
|
+
# properties:
|
443
|
+
# self : URI
|
444
|
+
# related : link
|
445
|
+
#
|
446
|
+
# description:
|
447
|
+
# A resource object **MAY** contain references to other resource objects ("relationships").
|
448
|
+
# Relationships may be to-one or to-many. Relationships can be specified by including a member
|
449
|
+
# in a resource's links object.
|
450
|
+
#
|
451
|
+
# A `self` member’s value is a URL for the relationship itself (a "relationship URL"). This
|
452
|
+
# URL allows the client to directly manipulate the relationship. For example, it would allow
|
453
|
+
# a client to remove an `author` from an `article` without deleting the people resource
|
454
|
+
# itself.
|
455
|
+
# structure:
|
456
|
+
# {
|
457
|
+
# self: 'http://example.com/etc',
|
458
|
+
# related: link
|
459
|
+
# }.reject! {|_,v| v.nil? }
|
460
|
+
def links_for(serializer)
|
461
|
+
serializer._links.each_with_object({}) do |(name, value), hash|
|
462
|
+
result = Link.new(serializer, value).as_json
|
463
|
+
hash[name] = result if result
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
# {http://jsonapi.org/format/#fetching-pagination Pagination Links}
|
468
|
+
# pagination
|
469
|
+
# definition:
|
470
|
+
# first : pageObject
|
471
|
+
# last : pageObject
|
472
|
+
# prev : pageObject
|
473
|
+
# next : pageObject
|
474
|
+
# structure:
|
475
|
+
# {
|
476
|
+
# first: pageObject,
|
477
|
+
# last: pageObject,
|
478
|
+
# prev: pageObject,
|
479
|
+
# next: pageObject
|
480
|
+
# }
|
481
|
+
#
|
482
|
+
# pageObject
|
483
|
+
# definition:
|
484
|
+
# oneOf
|
485
|
+
# URI
|
486
|
+
# null
|
487
|
+
#
|
488
|
+
# description:
|
489
|
+
# The <x> page of data
|
490
|
+
# structure:
|
491
|
+
# if has_page?
|
492
|
+
# 'http://example.com/some-page?page[number][x]'
|
493
|
+
# else
|
494
|
+
# nil
|
495
|
+
# end
|
496
|
+
# prs:
|
497
|
+
# https://github.com/rails-api/active_model_serializers/pull/1041
|
498
|
+
def pagination_links_for(serializer)
|
499
|
+
PaginationLinks.new(serializer.object, instance_options).as_json
|
500
|
+
end
|
501
|
+
|
502
|
+
# {http://jsonapi.org/format/#document-meta Docment Meta}
|
503
|
+
def meta_for(serializer)
|
504
|
+
Meta.new(serializer).as_json
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
509
|
+
# rubocop:enable Style/AsciiComments
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Caprese
|
4
|
+
module Versioning
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Get versioned module name of object (class)
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# version_module('OrdersController') => 'API::V1::OrdersController'
|
11
|
+
#
|
12
|
+
# @param [String] suffix the string to append, hypothetically a module in
|
13
|
+
# this version's module space
|
14
|
+
# @return [String] the versioned modulized name of the suffix passed in
|
15
|
+
def version_module(suffix = nil)
|
16
|
+
name = (self.is_a?(Class) ? self : self.class).name.deconstantize
|
17
|
+
|
18
|
+
name + (suffix ? "::#{suffix}" : '')
|
19
|
+
end
|
20
|
+
|
21
|
+
# Get versioned path (url)
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# version_path('orders') => 'api/v1/orders'
|
25
|
+
#
|
26
|
+
# @param [String] suffix the string to append, hypothetically an extension in
|
27
|
+
# this version's path space
|
28
|
+
# @return [String] the versioned path name of the suffix passed in
|
29
|
+
def version_path(suffix = nil)
|
30
|
+
version_module.downcase.split('::').push(suffix).reject(&:nil?).join('/')
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get version dot path (translations)
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# version_dot_path('orders') => 'api/v1/orders'
|
37
|
+
#
|
38
|
+
# @param [String] suffix the string to append, hypothetically an extension in
|
39
|
+
# this version's dot space
|
40
|
+
# @return [String] the versioned dot path of the suffix passed in
|
41
|
+
def version_dot_path(suffix = nil)
|
42
|
+
version_module.downcase.split('::').push(suffix).reject(&:nil?).join('.')
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get versioned underscore name (routes)
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# version_name('orders') => 'api_v1_orders'
|
49
|
+
#
|
50
|
+
# @param [String] suffix the string to append, hypothetically a name that
|
51
|
+
# needs to be versioned
|
52
|
+
# @return [String] the versioned name of the suffix passed in
|
53
|
+
def version_name(suffix = nil)
|
54
|
+
version_module.downcase.split('::').push(suffix).reject(&:nil?).join('_')
|
55
|
+
end
|
56
|
+
|
57
|
+
# Strips string to raw unversioned format regardless of input format
|
58
|
+
#
|
59
|
+
# @param [String] str the string to unversion
|
60
|
+
# @return [String] the stripped, unversioned name of the string passed in
|
61
|
+
def unversion(str)
|
62
|
+
str
|
63
|
+
.remove(version_module(''))
|
64
|
+
.remove(version_path(''))
|
65
|
+
.remove(version_name(''))
|
66
|
+
.remove(version_dot_path(''))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
# Defines callbacks like `before_create`, `after_create` to be called in abstracted persistence methods
|
4
|
+
# like `create`, so there is no need to override `create`, which won't work properly because of the `render`
|
5
|
+
# call in it
|
6
|
+
module Caprese
|
7
|
+
module Callbacks
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
CALLBACKS = [
|
11
|
+
:before_query, :after_query,
|
12
|
+
:after_initialize,
|
13
|
+
:before_create, :after_create,
|
14
|
+
:before_update, :after_update,
|
15
|
+
:before_save, :after_save,
|
16
|
+
:before_destroy, :after_destroy
|
17
|
+
]
|
18
|
+
|
19
|
+
included do
|
20
|
+
CALLBACKS.each do |method_name|
|
21
|
+
instance_variable_name = "@#{method_name}_callbacks"
|
22
|
+
|
23
|
+
# Defines instance method like `execute_before_create_callbacks` to be called with the resource
|
24
|
+
# being created passed in as an argument, to perform actions before saving the resource using
|
25
|
+
# callbacks defined at the class level
|
26
|
+
# @see below `define_single_method method_name`
|
27
|
+
#
|
28
|
+
# @note In the instance of `before_create`, the resource to be created will be passed in to callbacks,
|
29
|
+
# but in cases like `before_query`, nothing will be passed in, or `after_query`, the result of the
|
30
|
+
# query will be passed in
|
31
|
+
#
|
32
|
+
# @param [ActiveRecord::Base] resource the resource to perform actions on/with
|
33
|
+
define_method "execute_#{method_name}_callbacks" do |arg = nil|
|
34
|
+
self.class.instance_variable_get(instance_variable_name).try(:each) do |callback|
|
35
|
+
if self.class.instance_method(callback).arity > 0
|
36
|
+
send(callback, arg)
|
37
|
+
else
|
38
|
+
send(callback)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Defines class method like `before_create` that takes in symbols that specify
|
44
|
+
# callbacks to call at a certain even like before creating a resource
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# before_create :do_something, :do_another_thing
|
48
|
+
# after_create :do_more_things, :do_other_things
|
49
|
+
#
|
50
|
+
# @param [Symbol,Array<Symbol>] callbacks the name(s) of callbacks to add to list of callbacks
|
51
|
+
define_singleton_method method_name do |*callbacks|
|
52
|
+
unless (all_callbacks = self.instance_variable_get(instance_variable_name))
|
53
|
+
all_callbacks = self.instance_variable_set(instance_variable_name, [])
|
54
|
+
end
|
55
|
+
all_callbacks.push *callbacks
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|