jsonapi-resources 0.9.12 → 0.10.0.beta1

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