jsonapi-resources 0.9.0 → 0.10.6

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 +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/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 -105
  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 +71 -8
  14. data/lib/jsonapi/error.rb +27 -0
  15. data/lib/jsonapi/error_codes.rb +2 -0
  16. data/lib/jsonapi/exceptions.rb +80 -50
  17. data/lib/jsonapi/formatter.rb +3 -3
  18. data/lib/jsonapi/include_directives.rb +18 -65
  19. data/lib/jsonapi/link_builder.rb +74 -80
  20. data/lib/jsonapi/operation.rb +16 -5
  21. data/lib/jsonapi/operation_result.rb +74 -16
  22. data/lib/jsonapi/path.rb +43 -0
  23. data/lib/jsonapi/path_segment.rb +76 -0
  24. data/lib/jsonapi/processor.rb +239 -111
  25. data/lib/jsonapi/relationship.rb +153 -15
  26. data/lib/jsonapi/request_parser.rb +430 -367
  27. data/lib/jsonapi/resource.rb +3 -1253
  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 +143 -285
  33. data/lib/jsonapi/resource_set.rb +176 -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 +105 -83
  37. data/lib/jsonapi/routing_ext.rb +48 -26
  38. data/lib/jsonapi-resources.rb +20 -4
  39. data/lib/tasks/check_upgrade.rake +52 -0
  40. metadata +50 -20
  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,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)
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)
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
+ fail JSONAPI::Exceptions::RecordNotFound.new(id) if resource_set.resource_klasses.empty?
105
+ resource_set.populate!(serializer, context, find_options)
129
106
 
130
- return JSONAPI::ResourceOperationResult.new(:ok, resource_record)
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
 
139
- return JSONAPI::LinksObjectOperationResult.new(:ok,
140
- parent_resource,
141
- resource_klass._relationship(relationship_type))
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
+
134
+ return JSONAPI::RelationshipOperationResult.new(:ok,
135
+ parent_resource,
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)
154
162
 
155
- return JSONAPI::ResourceOperationResult.new(:ok, related_resource)
163
+ resource_set.populate!(serializer, context, find_options)
164
+
165
+ return JSONAPI::ResourceSetOperationResult.new(:ok, resource_set, result_options)
156
166
  end
157
167
 
158
168
  def show_related_resources
@@ -164,11 +174,12 @@ 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)
179
+ verified_filters = resource_klass.verify_filters(filters, context)
169
180
 
170
- rel_opts = {
171
- filters: filters,
181
+ find_options = {
182
+ filters: verified_filters,
172
183
  sort_criteria: sort_criteria,
173
184
  paginator: paginator,
174
185
  fields: fields,
@@ -176,62 +187,68 @@ module JSONAPI
176
187
  include_directives: include_directives
177
188
  }
178
189
 
179
- related_resources = nil
180
- if params[:cache_serializer]
181
- # TODO Could also avoid instantiating source_resource as actual Resource by
182
- # allowing LinkBuilder to accept CachedResourceFragment as source in
183
- # relationships_related_link
184
- scope = source_resource.public_send(:"records_for_#{relationship_type}", rel_opts)
185
- relationship = source_klass._relationship(relationship_type)
186
- related_resources = relationship.resource_klass.find_serialized_with_caching(
187
- scope,
188
- params[:cache_serializer],
189
- rel_opts
190
- )
191
- else
192
- related_resources = source_resource.public_send(relationship_type, rel_opts)
193
- end
190
+ source_resource = source_klass.find_by_key(source_id, context: context, fields: fields)
194
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)
198
+
199
+ opts = result_options
195
200
  if ((JSONAPI.configuration.top_level_meta_include_record_count) ||
196
201
  (paginator && paginator.class.requires_record_count) ||
197
202
  (JSONAPI.configuration.top_level_meta_include_page_count))
198
- related_resource_records = source_resource.public_send("records_for_" + relationship_type)
199
- records = resource_klass.filter_records(filters, {},
200
- related_resource_records)
201
203
 
202
- 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)
203
208
  end
204
209
 
205
- if (JSONAPI.configuration.top_level_meta_include_page_count && record_count)
206
- page_count = paginator.calculate_page_count(record_count)
210
+ if (JSONAPI.configuration.top_level_meta_include_page_count && opts[:record_count])
211
+ opts[:page_count] = paginator.calculate_page_count(opts[:record_count])
207
212
  end
208
213
 
209
- pagination_params = if paginator && JSONAPI.configuration.top_level_links_include_pagination
210
- page_options = {}
211
- page_options[:record_count] = record_count if paginator.class.requires_record_count
212
- paginator.links_page_params(page_options)
213
- else
214
- {}
215
- end
216
-
217
- opts = {}
218
- opts.merge!(pagination_params: pagination_params) if JSONAPI.configuration.top_level_links_include_pagination
219
- opts.merge!(record_count: record_count) if JSONAPI.configuration.top_level_meta_include_record_count
220
- opts.merge!(page_count: page_count) if JSONAPI.configuration.top_level_meta_include_page_count
221
-
222
- return JSONAPI::RelatedResourcesOperationResult.new(:ok,
223
- source_resource,
224
- relationship_type,
225
- related_resources,
226
- 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.class.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)
227
227
  end
228
228
 
229
229
  def create_resource
230
+ include_directives = params[:include_directives]
231
+ fields = params[:fields]
232
+ serializer = params[:serializer]
233
+
230
234
  data = params[:data]
231
235
  resource = resource_klass.create(context)
232
236
  result = resource.replace_fields(data)
233
237
 
234
- 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)
235
252
  end
236
253
 
237
254
  def remove_resource
@@ -240,17 +257,35 @@ module JSONAPI
240
257
  resource = resource_klass.find_by_key(resource_id, context: context)
241
258
  result = resource.remove
242
259
 
243
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
260
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
244
261
  end
245
262
 
246
263
  def replace_fields
247
264
  resource_id = params[:resource_id]
265
+ include_directives = params[:include_directives]
266
+ fields = params[:fields]
267
+ serializer = params[:serializer]
268
+
248
269
  data = params[:data]
249
270
 
250
271
  resource = resource_klass.find_by_key(resource_id, context: context)
272
+
251
273
  result = resource.replace_fields(data)
252
274
 
253
- 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)
254
289
  end
255
290
 
256
291
  def replace_to_one_relationship
@@ -261,7 +296,7 @@ module JSONAPI
261
296
  resource = resource_klass.find_by_key(resource_id, context: context)
262
297
  result = resource.replace_to_one_link(relationship_type, key_value)
263
298
 
264
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
299
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
265
300
  end
266
301
 
267
302
  def replace_polymorphic_to_one_relationship
@@ -273,7 +308,7 @@ module JSONAPI
273
308
  resource = resource_klass.find_by_key(resource_id, context: context)
274
309
  result = resource.replace_polymorphic_to_one_link(relationship_type, key_value, key_type)
275
310
 
276
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
311
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
277
312
  end
278
313
 
279
314
  def create_to_many_relationships
@@ -284,7 +319,7 @@ module JSONAPI
284
319
  resource = resource_klass.find_by_key(resource_id, context: context)
285
320
  result = resource.create_to_many_links(relationship_type, data)
286
321
 
287
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
322
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
288
323
  end
289
324
 
290
325
  def replace_to_many_relationships
@@ -295,7 +330,7 @@ module JSONAPI
295
330
  resource = resource_klass.find_by_key(resource_id, context: context)
296
331
  result = resource.replace_to_many_links(relationship_type, data)
297
332
 
298
- return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
333
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted, result_options)
299
334
  end
300
335
 
301
336
  def remove_to_many_relationships
@@ -312,7 +347,7 @@ module JSONAPI
312
347
  complete = false
313
348
  end
314
349
  end
315
- return JSONAPI::OperationResult.new(complete ? :no_content : :accepted)
350
+ return JSONAPI::OperationResult.new(complete ? :no_content : :accepted, result_options)
316
351
  end
317
352
 
318
353
  def remove_to_one_relationship
@@ -322,7 +357,100 @@ module JSONAPI
322
357
  resource = resource_klass.find_by_key(resource_id, context: context)
323
358
  result = resource.remove_to_one_link(relationship_type)
324
359
 
325
- 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
+ private
386
+ def find_related_resource_id_tree(resource_klass, source_id, relationship_name, find_options, include_related)
387
+ options = find_options.except(:include_directives)
388
+ options[:cache] = resource_klass.caching?
389
+
390
+ fragments = resource_klass.find_included_fragments([source_id], relationship_name, options)
391
+
392
+ primary_resource_id_tree = PrimaryResourceIdTree.new
393
+ primary_resource_id_tree.add_resource_fragments(fragments, include_related)
394
+
395
+ load_included(resource_klass, primary_resource_id_tree, include_related, options)
396
+
397
+ primary_resource_id_tree
398
+ end
399
+
400
+ def find_resource_id_tree(resource_klass, find_options, include_related)
401
+ options = find_options
402
+ options[:cache] = resource_klass.caching?
403
+
404
+ fragments = resource_klass.find_fragments(find_options[:filters], options)
405
+
406
+ primary_resource_id_tree = PrimaryResourceIdTree.new
407
+ primary_resource_id_tree.add_resource_fragments(fragments, include_related)
408
+
409
+ load_included(resource_klass, primary_resource_id_tree, include_related, options)
410
+
411
+ primary_resource_id_tree
412
+ end
413
+
414
+ def find_resource_id_tree_from_resource_relationship(resource, relationship_name, find_options, include_related)
415
+ relationship = resource.class._relationship(relationship_name)
416
+
417
+ options = find_options.except(:include_directives)
418
+ options[:cache] = relationship.resource_klass.caching?
419
+
420
+ fragments = resource.class.find_related_fragments([resource.identity], relationship_name, options)
421
+
422
+ primary_resource_id_tree = PrimaryResourceIdTree.new
423
+ primary_resource_id_tree.add_resource_fragments(fragments, include_related)
424
+
425
+ load_included(resource_klass, primary_resource_id_tree, include_related, options)
426
+
427
+ primary_resource_id_tree
428
+ end
429
+
430
+ def load_included(resource_klass, source_resource_id_tree, include_related, options)
431
+ source_rids = source_resource_id_tree.fragments.keys
432
+
433
+ include_related.try(:each_key) do |key|
434
+ relationship = resource_klass._relationship(key)
435
+ relationship_name = relationship.name.to_sym
436
+
437
+ find_related_resource_options = options.except(:filters, :sort_criteria, :paginator)
438
+ find_related_resource_options[:sort_criteria] = relationship.resource_klass.default_sort
439
+ find_related_resource_options[:cache] = resource_klass.caching?
440
+
441
+ related_fragments = resource_klass.find_included_fragments(
442
+ source_rids, relationship_name, find_related_resource_options
443
+ )
444
+
445
+ related_resource_id_tree = source_resource_id_tree.fetch_related_resource_id_tree(relationship)
446
+ related_resource_id_tree.add_resource_fragments(related_fragments, include_related[key][include_related])
447
+
448
+ # Now recursively get the related resources for the currently found resources
449
+ load_included(relationship.resource_klass,
450
+ related_resource_id_tree,
451
+ include_related[relationship_name][:include_related],
452
+ options)
453
+ end
326
454
  end
327
455
  end
328
456
  end