jsonapi-resources 0.9.11 → 0.10.7

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 (45) hide show
  1. checksums.yaml +4 -4
  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/active_relation/adapters/join_left_active_record_adapter.rb +27 -0
  7. data/lib/jsonapi/active_relation/join_manager.rb +303 -0
  8. data/lib/jsonapi/active_relation_resource.rb +884 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +122 -106
  10. data/lib/jsonapi/basic_resource.rb +1162 -0
  11. data/lib/jsonapi/cached_response_fragment.rb +127 -0
  12. data/lib/jsonapi/compiled_json.rb +11 -1
  13. data/lib/jsonapi/configuration.rb +57 -8
  14. data/lib/jsonapi/error.rb +27 -0
  15. data/lib/jsonapi/error_codes.rb +2 -0
  16. data/lib/jsonapi/exceptions.rb +63 -40
  17. data/lib/jsonapi/formatter.rb +3 -3
  18. data/lib/jsonapi/include_directives.rb +18 -75
  19. data/lib/jsonapi/link_builder.rb +16 -19
  20. data/lib/jsonapi/operation.rb +16 -5
  21. data/lib/jsonapi/operation_result.rb +73 -15
  22. data/lib/jsonapi/paginator.rb +17 -0
  23. data/lib/jsonapi/path.rb +43 -0
  24. data/lib/jsonapi/path_segment.rb +76 -0
  25. data/lib/jsonapi/processor.rb +246 -111
  26. data/lib/jsonapi/relationship.rb +117 -18
  27. data/lib/jsonapi/request_parser.rb +383 -396
  28. data/lib/jsonapi/resource.rb +3 -1376
  29. data/lib/jsonapi/resource_controller_metal.rb +5 -2
  30. data/lib/jsonapi/resource_fragment.rb +47 -0
  31. data/lib/jsonapi/resource_id_tree.rb +112 -0
  32. data/lib/jsonapi/resource_identity.rb +42 -0
  33. data/lib/jsonapi/resource_serializer.rb +124 -286
  34. data/lib/jsonapi/resource_set.rb +176 -0
  35. data/lib/jsonapi/resources/railtie.rb +9 -0
  36. data/lib/jsonapi/resources/version.rb +1 -1
  37. data/lib/jsonapi/response_document.rb +104 -87
  38. data/lib/jsonapi/routing_ext.rb +19 -21
  39. data/lib/jsonapi-resources.rb +20 -4
  40. data/lib/tasks/check_upgrade.rake +52 -0
  41. metadata +36 -33
  42. data/lib/jsonapi/cached_resource_fragment.rb +0 -127
  43. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  44. data/lib/jsonapi/operation_results.rb +0 -35
  45. 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)
@@ -66,93 +42,127 @@ module JSONAPI
66
42
  def find
67
43
  filters = params[:filters]
68
44
  include_directives = params[:include_directives]
69
- sort_criteria = params.fetch(:sort_criteria, [])
45
+ sort_criteria = params[: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 (top_level_meta_include_record_count || (paginator && paginator.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 (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 },
97
+ include_directives: include_directives
120
98
  }
121
99
 
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
100
+ resource_set = find_resource_set(resource_klass,
101
+ include_directives,
102
+ find_options)
129
103
 
130
- return JSONAPI::ResourceOperationResult.new(:ok, resource_record)
104
+ fail JSONAPI::Exceptions::RecordNotFound.new(id) if resource_set.resource_klasses.empty?
105
+ resource_set.populate!(serializer, context, find_options)
106
+
107
+ return JSONAPI::ResourceSetOperationResult.new(:ok, resource_set, result_options)
131
108
  end
132
109
 
133
110
  def show_relationship
134
111
  parent_key = params[:parent_key]
135
112
  relationship_type = params[:relationship_type].to_sym
113
+ paginator = params[:paginator]
114
+ sort_criteria = params[:sort_criteria]
115
+ include_directives = params[:include_directives]
116
+ fields = params[:fields]
136
117
 
137
118
  parent_resource = resource_klass.find_by_key(parent_key, context: context)
138
119
 
120
+ find_options = {
121
+ context: context,
122
+ sort_criteria: sort_criteria,
123
+ paginator: paginator,
124
+ fields: fields,
125
+ include_directives: include_directives
126
+ }
127
+
128
+ resource_id_tree = find_related_resource_id_tree(resource_klass,
129
+ JSONAPI::ResourceIdentity.new(resource_klass, parent_key),
130
+ relationship_type,
131
+ find_options,
132
+ nil)
133
+
139
134
  return JSONAPI::RelationshipOperationResult.new(:ok,
140
135
  parent_resource,
141
- resource_klass._relationship(relationship_type))
136
+ resource_klass._relationship(relationship_type),
137
+ resource_id_tree.fragments.keys,
138
+ result_options)
142
139
  end
143
140
 
144
141
  def show_related_resource
142
+ include_directives = params[:include_directives]
145
143
  source_klass = params[:source_klass]
146
144
  source_id = params[:source_id]
147
- relationship_type = params[:relationship_type].to_sym
145
+ relationship_type = params[:relationship_type]
146
+ serializer = params[:serializer]
148
147
  fields = params[:fields]
149
148
 
150
- # TODO Should fetch related_resource from cache if caching enabled
149
+ find_options = {
150
+ context: context,
151
+ fields: fields,
152
+ filters: {},
153
+ include_directives: include_directives
154
+ }
155
+
151
156
  source_resource = source_klass.find_by_key(source_id, context: context, fields: fields)
152
157
 
153
- related_resource = source_resource.public_send(relationship_type)
158
+ resource_set = find_related_resource_set(source_resource,
159
+ relationship_type,
160
+ include_directives,
161
+ find_options)
162
+
163
+ resource_set.populate!(serializer, context, find_options)
154
164
 
155
- return JSONAPI::ResourceOperationResult.new(:ok, related_resource)
165
+ return JSONAPI::ResourceSetOperationResult.new(:ok, resource_set, result_options)
156
166
  end
157
167
 
158
168
  def show_related_resources
@@ -164,11 +174,11 @@ module JSONAPI
164
174
  paginator = params[:paginator]
165
175
  fields = params[:fields]
166
176
  include_directives = params[:include_directives]
177
+ serializer = params[:serializer]
167
178
 
168
- source_resource ||= source_klass.find_by_key(source_id, context: context, fields: fields)
169
179
  verified_filters = resource_klass.verify_filters(filters, context)
170
180
 
171
- rel_opts = {
181
+ find_options = {
172
182
  filters: verified_filters,
173
183
  sort_criteria: sort_criteria,
174
184
  paginator: paginator,
@@ -177,62 +187,68 @@ module JSONAPI
177
187
  include_directives: include_directives
178
188
  }
179
189
 
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
190
+ source_resource = source_klass.find_by_key(source_id, context: context, fields: fields)
191
+
192
+ resource_set = find_related_resource_set(source_resource,
193
+ relationship_type,
194
+ include_directives,
195
+ find_options)
196
+
197
+ resource_set.populate!(serializer, context, find_options)
195
198
 
196
- if ((JSONAPI.configuration.top_level_meta_include_record_count) ||
197
- (paginator && paginator.class.requires_record_count) ||
198
- (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)
199
+ opts = result_options
200
+ if ((top_level_meta_include_record_count) ||
201
+ (paginator && paginator.requires_record_count) ||
202
+ (top_level_meta_include_page_count))
202
203
 
203
- record_count = resource_klass.count_records(records)
204
+ opts[:record_count] = source_resource.class.count_related(
205
+ source_resource.identity,
206
+ relationship_type,
207
+ find_options)
204
208
  end
205
209
 
206
- if (JSONAPI.configuration.top_level_meta_include_page_count && record_count)
207
- page_count = paginator.calculate_page_count(record_count)
210
+ if (top_level_meta_include_page_count && opts[:record_count])
211
+ opts[:page_count] = paginator.calculate_page_count(opts[:record_count])
208
212
  end
209
213
 
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)
214
+ opts[:pagination_params] = if paginator && JSONAPI.configuration.top_level_links_include_pagination
215
+ page_options = {}
216
+ page_options[:record_count] = opts[:record_count] if paginator.requires_record_count
217
+ paginator.links_page_params(page_options.merge(fetched_resources: resource_set))
218
+ else
219
+ {}
220
+ end
221
+
222
+ return JSONAPI::RelatedResourcesSetOperationResult.new(:ok,
223
+ source_resource,
224
+ relationship_type,
225
+ resource_set,
226
+ opts)
228
227
  end
229
228
 
230
229
  def create_resource
230
+ include_directives = params[:include_directives]
231
+ fields = params[:fields]
232
+ serializer = params[:serializer]
233
+
231
234
  data = params[:data]
232
235
  resource = resource_klass.create(context)
233
236
  result = resource.replace_fields(data)
234
237
 
235
- return JSONAPI::ResourceOperationResult.new((result == :completed ? :created : :accepted), resource)
238
+ find_options = {
239
+ context: context,
240
+ fields: fields,
241
+ filters: { resource_klass._primary_key => resource.id },
242
+ include_directives: include_directives
243
+ }
244
+
245
+ resource_set = find_resource_set(resource_klass,
246
+ include_directives,
247
+ find_options)
248
+
249
+ resource_set.populate!(serializer, context, find_options)
250
+
251
+ return JSONAPI::ResourceSetOperationResult.new((result == :completed ? :created : :accepted), resource_set, result_options)
236
252
  end
237
253
 
238
254
  def remove_resource
@@ -241,17 +257,35 @@ module JSONAPI
241
257
  resource = resource_klass.find_by_key(resource_id, context: context)
242
258
  result = resource.remove
243
259
 
244
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
260
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
245
261
  end
246
262
 
247
263
  def replace_fields
248
264
  resource_id = params[:resource_id]
265
+ include_directives = params[:include_directives]
266
+ fields = params[:fields]
267
+ serializer = params[:serializer]
268
+
249
269
  data = params[:data]
250
270
 
251
271
  resource = resource_klass.find_by_key(resource_id, context: context)
272
+
252
273
  result = resource.replace_fields(data)
253
274
 
254
- return JSONAPI::ResourceOperationResult.new(result == :completed ? :ok : :accepted, resource)
275
+ find_options = {
276
+ context: context,
277
+ fields: fields,
278
+ filters: { resource_klass._primary_key => resource.id },
279
+ include_directives: include_directives
280
+ }
281
+
282
+ resource_set = find_resource_set(resource_klass,
283
+ include_directives,
284
+ find_options)
285
+
286
+ resource_set.populate!(serializer, context, find_options)
287
+
288
+ return JSONAPI::ResourceSetOperationResult.new((result == :completed ? :ok : :accepted), resource_set, result_options)
255
289
  end
256
290
 
257
291
  def replace_to_one_relationship
@@ -262,7 +296,7 @@ module JSONAPI
262
296
  resource = resource_klass.find_by_key(resource_id, context: context)
263
297
  result = resource.replace_to_one_link(relationship_type, key_value)
264
298
 
265
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
299
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
266
300
  end
267
301
 
268
302
  def replace_polymorphic_to_one_relationship
@@ -274,7 +308,7 @@ module JSONAPI
274
308
  resource = resource_klass.find_by_key(resource_id, context: context)
275
309
  result = resource.replace_polymorphic_to_one_link(relationship_type, key_value, key_type)
276
310
 
277
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
311
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
278
312
  end
279
313
 
280
314
  def create_to_many_relationships
@@ -285,7 +319,7 @@ module JSONAPI
285
319
  resource = resource_klass.find_by_key(resource_id, context: context)
286
320
  result = resource.create_to_many_links(relationship_type, data)
287
321
 
288
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
322
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
289
323
  end
290
324
 
291
325
  def replace_to_many_relationships
@@ -296,7 +330,7 @@ module JSONAPI
296
330
  resource = resource_klass.find_by_key(resource_id, context: context)
297
331
  result = resource.replace_to_many_links(relationship_type, data)
298
332
 
299
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
333
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
300
334
  end
301
335
 
302
336
  def remove_to_many_relationships
@@ -313,7 +347,7 @@ module JSONAPI
313
347
  complete = false
314
348
  end
315
349
  end
316
- return JSONAPI::OperationResult.new(complete ? :no_content : :accepted)
350
+ return JSONAPI::OperationResult.new(complete ? :no_content : :accepted, result_options)
317
351
  end
318
352
 
319
353
  def remove_to_one_relationship
@@ -323,7 +357,108 @@ module JSONAPI
323
357
  resource = resource_klass.find_by_key(resource_id, context: context)
324
358
  result = resource.remove_to_one_link(relationship_type)
325
359
 
326
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
360
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
361
+ end
362
+
363
+ def result_options
364
+ options = {}
365
+ options[:warnings] = params[:warnings] if params[:warnings]
366
+ options
367
+ end
368
+
369
+ def find_resource_set(resource_klass, include_directives, options)
370
+ include_related = include_directives.include_directives[:include_related] if include_directives
371
+
372
+ resource_id_tree = find_resource_id_tree(resource_klass, options, include_related)
373
+
374
+ JSONAPI::ResourceSet.new(resource_id_tree)
375
+ end
376
+
377
+ def find_related_resource_set(resource, relationship_name, include_directives, options)
378
+ include_related = include_directives.include_directives[:include_related] if include_directives
379
+
380
+ resource_id_tree = find_resource_id_tree_from_resource_relationship(resource, relationship_name, options, include_related)
381
+
382
+ JSONAPI::ResourceSet.new(resource_id_tree)
383
+ end
384
+
385
+ def top_level_meta_include_record_count
386
+ JSONAPI.configuration.top_level_meta_include_record_count
387
+ end
388
+
389
+ def top_level_meta_include_page_count
390
+ JSONAPI.configuration.top_level_meta_include_page_count
391
+ end
392
+
393
+ private
394
+ def find_related_resource_id_tree(resource_klass, source_id, relationship_name, find_options, include_related)
395
+ options = find_options.except(:include_directives)
396
+ options[:cache] = resource_klass.caching?
397
+
398
+ fragments = resource_klass.find_included_fragments([source_id], relationship_name, options)
399
+
400
+ primary_resource_id_tree = PrimaryResourceIdTree.new
401
+ primary_resource_id_tree.add_resource_fragments(fragments, include_related)
402
+
403
+ load_included(resource_klass, primary_resource_id_tree, include_related, options)
404
+
405
+ primary_resource_id_tree
406
+ end
407
+
408
+ def find_resource_id_tree(resource_klass, find_options, include_related)
409
+ options = find_options
410
+ options[:cache] = resource_klass.caching?
411
+
412
+ fragments = resource_klass.find_fragments(find_options[:filters], options)
413
+
414
+ primary_resource_id_tree = PrimaryResourceIdTree.new
415
+ primary_resource_id_tree.add_resource_fragments(fragments, include_related)
416
+
417
+ load_included(resource_klass, primary_resource_id_tree, include_related, options)
418
+
419
+ primary_resource_id_tree
420
+ end
421
+
422
+ def find_resource_id_tree_from_resource_relationship(resource, relationship_name, find_options, include_related)
423
+ relationship = resource.class._relationship(relationship_name)
424
+
425
+ options = find_options.except(:include_directives)
426
+ options[:cache] = relationship.resource_klass.caching?
427
+
428
+ fragments = resource.class.find_related_fragments([resource.identity], relationship_name, options)
429
+
430
+ primary_resource_id_tree = PrimaryResourceIdTree.new
431
+ primary_resource_id_tree.add_resource_fragments(fragments, include_related)
432
+
433
+ load_included(resource_klass, primary_resource_id_tree, include_related, options)
434
+
435
+ primary_resource_id_tree
436
+ end
437
+
438
+ def load_included(resource_klass, source_resource_id_tree, include_related, options)
439
+ source_rids = source_resource_id_tree.fragments.keys
440
+
441
+ include_related.try(:each_key) do |key|
442
+ relationship = resource_klass._relationship(key)
443
+ relationship_name = relationship.name.to_sym
444
+
445
+ find_related_resource_options = options.except(:filters, :sort_criteria, :paginator)
446
+ find_related_resource_options[:sort_criteria] = relationship.resource_klass.default_sort
447
+ find_related_resource_options[:cache] = resource_klass.caching?
448
+
449
+ related_fragments = resource_klass.find_included_fragments(
450
+ source_rids, relationship_name, find_related_resource_options
451
+ )
452
+
453
+ related_resource_id_tree = source_resource_id_tree.fetch_related_resource_id_tree(relationship)
454
+ related_resource_id_tree.add_resource_fragments(related_fragments, include_related[key][include_related])
455
+
456
+ # Now recursively get the related resources for the currently found resources
457
+ load_included(relationship.resource_klass,
458
+ related_resource_id_tree,
459
+ include_related[relationship_name][:include_related],
460
+ options)
461
+ end
327
462
  end
328
463
  end
329
464
  end