jsonapi-resources 0.9.12 → 0.10.0.beta1

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 (36) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +1 -1
  3. data/README.md +34 -11
  4. data/lib/bug_report_templates/rails_5_latest.rb +125 -0
  5. data/lib/bug_report_templates/rails_5_master.rb +140 -0
  6. data/lib/jsonapi-resources.rb +8 -3
  7. data/lib/jsonapi/active_relation_resource_finder.rb +640 -0
  8. data/lib/jsonapi/active_relation_resource_finder/join_tree.rb +126 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +121 -106
  10. data/lib/jsonapi/{cached_resource_fragment.rb → cached_response_fragment.rb} +13 -30
  11. data/lib/jsonapi/compiled_json.rb +11 -1
  12. data/lib/jsonapi/configuration.rb +44 -18
  13. data/lib/jsonapi/error.rb +27 -0
  14. data/lib/jsonapi/exceptions.rb +43 -40
  15. data/lib/jsonapi/formatter.rb +3 -3
  16. data/lib/jsonapi/include_directives.rb +2 -45
  17. data/lib/jsonapi/link_builder.rb +87 -80
  18. data/lib/jsonapi/operation.rb +16 -5
  19. data/lib/jsonapi/operation_result.rb +74 -16
  20. data/lib/jsonapi/processor.rb +233 -112
  21. data/lib/jsonapi/relationship.rb +77 -53
  22. data/lib/jsonapi/request_parser.rb +378 -423
  23. data/lib/jsonapi/resource.rb +224 -524
  24. data/lib/jsonapi/resource_controller_metal.rb +2 -2
  25. data/lib/jsonapi/resource_fragment.rb +47 -0
  26. data/lib/jsonapi/resource_id_tree.rb +112 -0
  27. data/lib/jsonapi/resource_identity.rb +42 -0
  28. data/lib/jsonapi/resource_serializer.rb +133 -301
  29. data/lib/jsonapi/resource_set.rb +108 -0
  30. data/lib/jsonapi/resources/version.rb +1 -1
  31. data/lib/jsonapi/response_document.rb +100 -88
  32. data/lib/jsonapi/routing_ext.rb +21 -43
  33. metadata +29 -45
  34. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  35. data/lib/jsonapi/operation_results.rb +0 -35
  36. data/lib/jsonapi/relationship_builder.rb +0 -167
@@ -17,30 +17,6 @@ module JSONAPI
17
17
  :remove_to_one_relationship,
18
18
  :operation
19
19
 
20
- class << self
21
- def processor_instance_for(resource_klass, operation_type, params)
22
- _processor_from_resource_type(resource_klass).new(resource_klass, operation_type, params)
23
- end
24
-
25
- def _processor_from_resource_type(resource_klass)
26
- processor = resource_klass.name.gsub(/Resource$/,'Processor').safe_constantize
27
- if processor.nil?
28
- processor = JSONAPI.configuration.default_processor_klass
29
- end
30
-
31
- return processor
32
- end
33
-
34
- def transactional_operation_type?(operation_type)
35
- case operation_type
36
- when :find, :show, :show_related_resource, :show_related_resources
37
- return false
38
- else
39
- return true
40
- end
41
- end
42
- end
43
-
44
20
  attr_reader :resource_klass, :operation_type, :params, :context, :result, :result_options
45
21
 
46
22
  def initialize(resource_klass, operation_type, params)
@@ -69,90 +45,120 @@ module JSONAPI
69
45
  sort_criteria = params.fetch(:sort_criteria, [])
70
46
  paginator = params[:paginator]
71
47
  fields = params[:fields]
48
+ serializer = params[:serializer]
72
49
 
73
50
  verified_filters = resource_klass.verify_filters(filters, context)
51
+
74
52
  find_options = {
75
53
  context: context,
76
- include_directives: include_directives,
77
54
  sort_criteria: sort_criteria,
78
55
  paginator: paginator,
79
- fields: fields
56
+ fields: fields,
57
+ filters: verified_filters,
58
+ include_directives: include_directives
80
59
  }
81
60
 
82
- resource_records = if params[:cache_serializer]
83
- resource_klass.find_serialized_with_caching(verified_filters,
84
- params[:cache_serializer],
85
- find_options)
86
- else
87
- resource_klass.find(verified_filters, find_options)
88
- end
61
+ resource_set = find_resource_set(resource_klass,
62
+ include_directives,
63
+ find_options)
64
+
65
+ resource_set.populate!(serializer, context, find_options)
89
66
 
90
- page_options = {}
91
- if (JSONAPI.configuration.top_level_meta_include_record_count ||
92
- (paginator && paginator.class.requires_record_count))
93
- page_options[:record_count] = resource_klass.find_count(verified_filters,
94
- context: context,
95
- include_directives: include_directives)
67
+ page_options = result_options
68
+ if (JSONAPI.configuration.top_level_meta_include_record_count || (paginator && paginator.class.requires_record_count))
69
+ page_options[:record_count] = resource_klass.count(verified_filters,
70
+ context: context,
71
+ include_directives: include_directives)
96
72
  end
97
73
 
98
- if (JSONAPI.configuration.top_level_meta_include_page_count && page_options[:record_count])
74
+ if (JSONAPI.configuration.top_level_meta_include_page_count && paginator && page_options[:record_count])
99
75
  page_options[:page_count] = paginator ? paginator.calculate_page_count(page_options[:record_count]) : 1
100
76
  end
101
77
 
102
78
  if JSONAPI.configuration.top_level_links_include_pagination && paginator
103
- page_options[:pagination_params] = paginator.links_page_params(page_options.merge(fetched_resources: resource_records))
79
+ page_options[:pagination_params] = paginator.links_page_params(page_options.merge(fetched_resources: resource_set))
104
80
  end
105
81
 
106
- return JSONAPI::ResourcesOperationResult.new(:ok, resource_records, page_options)
82
+ return JSONAPI::ResourcesSetOperationResult.new(:ok, resource_set, page_options)
107
83
  end
108
84
 
109
85
  def show
110
86
  include_directives = params[:include_directives]
111
87
  fields = params[:fields]
112
88
  id = params[:id]
89
+ serializer = params[:serializer]
113
90
 
114
91
  key = resource_klass.verify_key(id, context)
115
92
 
116
93
  find_options = {
117
94
  context: context,
118
- include_directives: include_directives,
119
- fields: fields
95
+ fields: fields,
96
+ filters: { resource_klass._primary_key => key }
120
97
  }
121
98
 
122
- resource_record = if params[:cache_serializer]
123
- resource_klass.find_by_key_serialized_with_caching(key,
124
- params[:cache_serializer],
125
- find_options)
126
- else
127
- resource_klass.find_by_key(key, find_options)
128
- end
99
+ resource_set = find_resource_set(resource_klass,
100
+ include_directives,
101
+ find_options)
102
+
103
+ resource_set.populate!(serializer, context, find_options)
129
104
 
130
- return JSONAPI::ResourceOperationResult.new(:ok, resource_record)
105
+ return JSONAPI::ResourceSetOperationResult.new(:ok, resource_set, result_options)
131
106
  end
132
107
 
133
108
  def show_relationship
134
109
  parent_key = params[:parent_key]
135
110
  relationship_type = params[:relationship_type].to_sym
111
+ paginator = params[:paginator]
112
+ sort_criteria = params.fetch(:sort_criteria, [])
113
+ include_directives = params[:include_directives]
114
+ fields = params[:fields]
136
115
 
137
116
  parent_resource = resource_klass.find_by_key(parent_key, context: context)
138
117
 
139
- return JSONAPI::RelationshipOperationResult.new(:ok,
140
- parent_resource,
141
- resource_klass._relationship(relationship_type))
118
+ find_options = {
119
+ context: context,
120
+ sort_criteria: sort_criteria,
121
+ paginator: paginator,
122
+ fields: fields
123
+ }
124
+
125
+ resource_id_tree = find_related_resource_id_tree(resource_klass,
126
+ JSONAPI::ResourceIdentity.new(resource_klass, parent_key),
127
+ relationship_type,
128
+ find_options,
129
+ nil)
130
+
131
+ return JSONAPI::LinksObjectOperationResult.new(:ok,
132
+ parent_resource,
133
+ resource_klass._relationship(relationship_type),
134
+ resource_id_tree.fragments.keys,
135
+ result_options)
142
136
  end
143
137
 
144
138
  def show_related_resource
139
+ include_directives = params[:include_directives]
145
140
  source_klass = params[:source_klass]
146
141
  source_id = params[:source_id]
147
- relationship_type = params[:relationship_type].to_sym
142
+ relationship_type = params[:relationship_type]
143
+ serializer = params[:serializer]
148
144
  fields = params[:fields]
149
145
 
150
- # TODO Should fetch related_resource from cache if caching enabled
146
+ find_options = {
147
+ context: context,
148
+ fields: fields,
149
+ filters: {}
150
+ }
151
+
151
152
  source_resource = source_klass.find_by_key(source_id, context: context, fields: fields)
152
153
 
153
- related_resource = source_resource.public_send(relationship_type)
154
+ resource_set = find_related_resource_set(source_resource,
155
+ relationship_type,
156
+ include_directives,
157
+ find_options)
158
+
159
+ resource_set.populate!(serializer, context, find_options)
154
160
 
155
- return JSONAPI::ResourceOperationResult.new(:ok, related_resource)
161
+ return JSONAPI::ResourceSetOperationResult.new(:ok, resource_set, result_options)
156
162
  end
157
163
 
158
164
  def show_related_resources
@@ -160,79 +166,83 @@ module JSONAPI
160
166
  source_id = params[:source_id]
161
167
  relationship_type = params[:relationship_type]
162
168
  filters = params[:filters]
163
- sort_criteria = params[:sort_criteria]
169
+ sort_criteria = params.fetch(:sort_criteria, resource_klass.default_sort)
164
170
  paginator = params[:paginator]
165
171
  fields = params[:fields]
166
172
  include_directives = params[:include_directives]
173
+ serializer = params[:serializer]
167
174
 
168
- source_resource ||= source_klass.find_by_key(source_id, context: context, fields: fields)
169
175
  verified_filters = resource_klass.verify_filters(filters, context)
170
176
 
171
- rel_opts = {
177
+ find_options = {
172
178
  filters: verified_filters,
173
179
  sort_criteria: sort_criteria,
174
180
  paginator: paginator,
175
181
  fields: fields,
176
- context: context,
177
- include_directives: include_directives
182
+ context: context
178
183
  }
179
184
 
180
- related_resources = nil
181
- if params[:cache_serializer]
182
- # TODO Could also avoid instantiating source_resource as actual Resource by
183
- # allowing LinkBuilder to accept CachedResourceFragment as source in
184
- # relationships_related_link
185
- scope = source_resource.public_send(:"records_for_#{relationship_type}", rel_opts)
186
- relationship = source_klass._relationship(relationship_type)
187
- related_resources = relationship.resource_klass.find_serialized_with_caching(
188
- scope,
189
- params[:cache_serializer],
190
- rel_opts
191
- )
192
- else
193
- related_resources = source_resource.public_send(relationship_type, rel_opts)
194
- end
185
+ source_resource = source_klass.find_by_key(source_id, context: context, fields: fields)
186
+
187
+ resource_set = find_related_resource_set(source_resource,
188
+ relationship_type,
189
+ include_directives,
190
+ find_options)
195
191
 
192
+ resource_set.populate!(serializer, context, find_options)
193
+
194
+ opts = result_options
196
195
  if ((JSONAPI.configuration.top_level_meta_include_record_count) ||
197
196
  (paginator && paginator.class.requires_record_count) ||
198
197
  (JSONAPI.configuration.top_level_meta_include_page_count))
199
- related_resource_records = source_resource.public_send("records_for_" + relationship_type)
200
- records = resource_klass.filter_records(verified_filters, rel_opts,
201
- related_resource_records)
202
198
 
203
- record_count = resource_klass.count_records(records)
199
+ opts[:record_count] = source_resource.class.count_related(
200
+ source_resource.identity,
201
+ relationship_type,
202
+ find_options)
204
203
  end
205
204
 
206
- if (JSONAPI.configuration.top_level_meta_include_page_count && record_count)
207
- page_count = paginator.calculate_page_count(record_count)
205
+ if (JSONAPI.configuration.top_level_meta_include_page_count && opts[:record_count])
206
+ opts[:page_count] = paginator.calculate_page_count(opts[:record_count])
208
207
  end
209
208
 
210
- pagination_params = if paginator && JSONAPI.configuration.top_level_links_include_pagination
211
- page_options = {}
212
- page_options[:record_count] = record_count if paginator.class.requires_record_count
213
- paginator.links_page_params(page_options.merge(fetched_resources: related_resources))
214
- else
215
- {}
216
- end
217
-
218
- opts = {}
219
- opts.merge!(pagination_params: pagination_params) if JSONAPI.configuration.top_level_links_include_pagination
220
- opts.merge!(record_count: record_count) if JSONAPI.configuration.top_level_meta_include_record_count
221
- opts.merge!(page_count: page_count) if JSONAPI.configuration.top_level_meta_include_page_count
222
-
223
- return JSONAPI::RelatedResourcesOperationResult.new(:ok,
224
- source_resource,
225
- relationship_type,
226
- related_resources,
227
- opts)
209
+ opts[:pagination_params] = if paginator && JSONAPI.configuration.top_level_links_include_pagination
210
+ page_options = {}
211
+ page_options[:record_count] = opts[:record_count] if paginator.class.requires_record_count
212
+ paginator.links_page_params(page_options.merge(fetched_resources: resource_set))
213
+ else
214
+ {}
215
+ end
216
+
217
+ return JSONAPI::RelatedResourcesSetOperationResult.new(:ok,
218
+ source_resource,
219
+ relationship_type,
220
+ resource_set,
221
+ opts)
228
222
  end
229
223
 
230
224
  def create_resource
225
+ include_directives = params[:include_directives]
226
+ fields = params[:fields]
227
+ serializer = params[:serializer]
228
+
231
229
  data = params[:data]
232
230
  resource = resource_klass.create(context)
233
231
  result = resource.replace_fields(data)
234
232
 
235
- return JSONAPI::ResourceOperationResult.new((result == :completed ? :created : :accepted), resource)
233
+ find_options = {
234
+ context: context,
235
+ fields: fields,
236
+ filters: { resource_klass._primary_key => resource.id }
237
+ }
238
+
239
+ resource_set = find_resource_set(resource_klass,
240
+ include_directives,
241
+ find_options)
242
+
243
+ resource_set.populate!(serializer, context, find_options)
244
+
245
+ return JSONAPI::ResourceSetOperationResult.new((result == :completed ? :created : :accepted), resource_set, result_options)
236
246
  end
237
247
 
238
248
  def remove_resource
@@ -241,17 +251,34 @@ module JSONAPI
241
251
  resource = resource_klass.find_by_key(resource_id, context: context)
242
252
  result = resource.remove
243
253
 
244
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
254
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
245
255
  end
246
256
 
247
257
  def replace_fields
248
258
  resource_id = params[:resource_id]
259
+ include_directives = params[:include_directives]
260
+ fields = params[:fields]
261
+ serializer = params[:serializer]
262
+
249
263
  data = params[:data]
250
264
 
251
265
  resource = resource_klass.find_by_key(resource_id, context: context)
266
+
252
267
  result = resource.replace_fields(data)
253
268
 
254
- return JSONAPI::ResourceOperationResult.new(result == :completed ? :ok : :accepted, resource)
269
+ find_options = {
270
+ context: context,
271
+ fields: fields,
272
+ filters: { resource_klass._primary_key => resource.id }
273
+ }
274
+
275
+ resource_set = find_resource_set(resource_klass,
276
+ include_directives,
277
+ find_options)
278
+
279
+ resource_set.populate!(serializer, context, find_options)
280
+
281
+ return JSONAPI::ResourceSetOperationResult.new((result == :completed ? :ok : :accepted), resource_set, result_options)
255
282
  end
256
283
 
257
284
  def replace_to_one_relationship
@@ -262,7 +289,7 @@ module JSONAPI
262
289
  resource = resource_klass.find_by_key(resource_id, context: context)
263
290
  result = resource.replace_to_one_link(relationship_type, key_value)
264
291
 
265
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
292
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
266
293
  end
267
294
 
268
295
  def replace_polymorphic_to_one_relationship
@@ -274,7 +301,7 @@ module JSONAPI
274
301
  resource = resource_klass.find_by_key(resource_id, context: context)
275
302
  result = resource.replace_polymorphic_to_one_link(relationship_type, key_value, key_type)
276
303
 
277
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
304
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
278
305
  end
279
306
 
280
307
  def create_to_many_relationships
@@ -285,7 +312,7 @@ module JSONAPI
285
312
  resource = resource_klass.find_by_key(resource_id, context: context)
286
313
  result = resource.create_to_many_links(relationship_type, data)
287
314
 
288
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
315
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
289
316
  end
290
317
 
291
318
  def replace_to_many_relationships
@@ -296,7 +323,7 @@ module JSONAPI
296
323
  resource = resource_klass.find_by_key(resource_id, context: context)
297
324
  result = resource.replace_to_many_links(relationship_type, data)
298
325
 
299
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
326
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
300
327
  end
301
328
 
302
329
  def remove_to_many_relationships
@@ -313,7 +340,7 @@ module JSONAPI
313
340
  complete = false
314
341
  end
315
342
  end
316
- return JSONAPI::OperationResult.new(complete ? :no_content : :accepted)
343
+ return JSONAPI::OperationResult.new(complete ? :no_content : :accepted, result_options)
317
344
  end
318
345
 
319
346
  def remove_to_one_relationship
@@ -323,7 +350,101 @@ module JSONAPI
323
350
  resource = resource_klass.find_by_key(resource_id, context: context)
324
351
  result = resource.remove_to_one_link(relationship_type)
325
352
 
326
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
353
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
354
+ end
355
+
356
+ def result_options
357
+ options = {}
358
+ options[:warnings] = params[:warnings] if params[:warnings]
359
+ options
360
+ end
361
+
362
+ def find_resource_set(resource_klass, include_directives, options)
363
+ include_related = include_directives.include_directives[:include_related] if include_directives
364
+
365
+ resource_id_tree = find_resource_id_tree(resource_klass, options, include_related)
366
+
367
+ JSONAPI::ResourceSet.new(resource_id_tree)
368
+ end
369
+
370
+ def find_related_resource_set(resource, relationship_name, include_directives, options)
371
+ include_related = include_directives.include_directives[:include_related] if include_directives
372
+
373
+ resource_id_tree = find_resource_id_tree_from_resource_relationship(resource, relationship_name, options, include_related)
374
+
375
+ JSONAPI::ResourceSet.new(resource_id_tree)
376
+ end
377
+
378
+ private
379
+ def find_related_resource_id_tree(resource_klass, source_id, relationship_name, find_options, include_related)
380
+ options = find_options.except(:include_directives)
381
+ options[:cache] = resource_klass.caching?
382
+
383
+ fragments = resource_klass.find_included_fragments([source_id], relationship_name, options)
384
+
385
+ primary_resource_id_tree = PrimaryResourceIdTree.new
386
+ primary_resource_id_tree.add_resource_fragments(fragments, include_related)
387
+
388
+ load_included(resource_klass, primary_resource_id_tree, include_related, options.except(:filters, :sort_criteria))
389
+
390
+ primary_resource_id_tree
391
+ end
392
+
393
+ def find_resource_id_tree(resource_klass, find_options, include_related)
394
+ options = find_options
395
+ options[:cache] = resource_klass.caching?
396
+
397
+ fragments = resource_klass.find_fragments(find_options[:filters], options)
398
+
399
+ primary_resource_id_tree = PrimaryResourceIdTree.new
400
+ primary_resource_id_tree.add_resource_fragments(fragments, include_related)
401
+
402
+ load_included(resource_klass, primary_resource_id_tree, include_related, options.except(:filters, :sort_criteria))
403
+
404
+ primary_resource_id_tree
405
+ end
406
+
407
+ def find_resource_id_tree_from_resource_relationship(resource, relationship_name, find_options, include_related)
408
+ relationship = resource.class._relationship(relationship_name)
409
+
410
+ options = find_options.except(:include_directives)
411
+ options[:cache] = relationship.resource_klass.caching?
412
+
413
+ fragments = resource.class.find_related_fragments([resource.identity], relationship_name, options)
414
+
415
+ primary_resource_id_tree = PrimaryResourceIdTree.new
416
+ primary_resource_id_tree.add_resource_fragments(fragments, include_related)
417
+
418
+ load_included(resource_klass, primary_resource_id_tree, include_related, options.except(:filters, :sort_criteria))
419
+
420
+ primary_resource_id_tree
421
+ end
422
+
423
+ def load_included(resource_klass, source_resource_id_tree, include_related, options)
424
+ source_rids = source_resource_id_tree.fragments.keys
425
+
426
+ include_related.try(:each_pair) do |key, value|
427
+ next unless value[:include]
428
+ relationship = resource_klass._relationship(key)
429
+ relationship_name = relationship.name.to_sym
430
+
431
+ find_related_resource_options = options.dup
432
+ find_related_resource_options[:sort_criteria] = relationship.resource_klass.default_sort
433
+ find_related_resource_options[:cache] = resource_klass.caching?
434
+
435
+ related_fragments = resource_klass.find_included_fragments(
436
+ source_rids, relationship_name, find_related_resource_options
437
+ )
438
+
439
+ related_resource_id_tree = source_resource_id_tree.fetch_related_resource_id_tree(relationship)
440
+ related_resource_id_tree.add_resource_fragments(related_fragments, include_related[key][include_related])
441
+
442
+ # Now recursively get the related resources for the currently found resources
443
+ load_included(relationship.resource_klass,
444
+ related_resource_id_tree,
445
+ include_related[relationship_name][:include_related],
446
+ options)
447
+ end
327
448
  end
328
449
  end
329
450
  end