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