jsonapi-resources 0.9.0 → 0.10.6

Sign up to get free protection for your applications and to get access to all the features.
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