jsonapi-resources 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +274 -102
  3. data/jsonapi-resources.gemspec +1 -0
  4. data/lib/jsonapi-resources.rb +15 -0
  5. data/lib/jsonapi/active_record_operations_processor.rb +21 -10
  6. data/lib/jsonapi/acts_as_resource_controller.rb +175 -0
  7. data/lib/jsonapi/configuration.rb +11 -0
  8. data/lib/jsonapi/error_codes.rb +7 -4
  9. data/lib/jsonapi/exceptions.rb +23 -15
  10. data/lib/jsonapi/formatter.rb +5 -5
  11. data/lib/jsonapi/include_directives.rb +67 -0
  12. data/lib/jsonapi/operation.rb +185 -65
  13. data/lib/jsonapi/operation_result.rb +38 -5
  14. data/lib/jsonapi/operation_results.rb +33 -0
  15. data/lib/jsonapi/operations_processor.rb +49 -9
  16. data/lib/jsonapi/paginator.rb +31 -17
  17. data/lib/jsonapi/request.rb +347 -163
  18. data/lib/jsonapi/resource.rb +159 -56
  19. data/lib/jsonapi/resource_controller.rb +1 -234
  20. data/lib/jsonapi/resource_serializer.rb +55 -69
  21. data/lib/jsonapi/resources/version.rb +1 -1
  22. data/lib/jsonapi/response_document.rb +87 -0
  23. data/lib/jsonapi/routing_ext.rb +17 -11
  24. data/test/controllers/controller_test.rb +602 -326
  25. data/test/fixtures/active_record.rb +96 -6
  26. data/test/fixtures/line_items.yml +7 -1
  27. data/test/fixtures/numeros_telefone.yml +3 -0
  28. data/test/fixtures/purchase_orders.yml +6 -0
  29. data/test/integration/requests/request_test.rb +129 -60
  30. data/test/integration/routes/routes_test.rb +17 -17
  31. data/test/test_helper.rb +23 -5
  32. data/test/unit/jsonapi_request/jsonapi_request_test.rb +48 -0
  33. data/test/unit/operation/operations_processor_test.rb +242 -54
  34. data/test/unit/resource/resource_test.rb +108 -2
  35. data/test/unit/serializer/include_directives_test.rb +108 -0
  36. data/test/unit/serializer/response_document_test.rb +61 -0
  37. data/test/unit/serializer/serializer_test.rb +679 -520
  38. metadata +26 -2
@@ -3,7 +3,7 @@ module JSONAPI
3
3
  def initialize(params)
4
4
  end
5
5
 
6
- def apply(relation)
6
+ def apply(relation, order_options)
7
7
  # relation
8
8
  end
9
9
 
@@ -19,9 +19,10 @@ end
19
19
  class OffsetPaginator < JSONAPI::Paginator
20
20
  def initialize(params)
21
21
  parse_pagination_params(params)
22
+ verify_pagination_params
22
23
  end
23
24
 
24
- def apply(relation)
25
+ def apply(relation, order_options)
25
26
  relation.offset(@offset).limit(@limit)
26
27
  end
27
28
 
@@ -35,27 +36,34 @@ class OffsetPaginator < JSONAPI::Paginator
35
36
 
36
37
  @offset = validparams[:offset] ? validparams[:offset].to_i : 0
37
38
  @limit = validparams[:limit] ? validparams[:limit].to_i : JSONAPI.configuration.default_page_size
38
-
39
- if @limit < 1
40
- raise JSONAPI::Exceptions::InvalidPageValue.new(:limit, validparams[:limit])
41
- elsif @limit > JSONAPI.configuration.maximum_page_size
42
- raise JSONAPI::Exceptions::InvalidPageValue.new(:limit, validparams[:limit],
43
- "Limit exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
44
- end
45
39
  else
46
40
  raise JSONAPI::Exceptions::InvalidPageObject.new
47
41
  end
48
42
  rescue ActionController::UnpermittedParameters => e
49
43
  raise JSONAPI::Exceptions::PageParametersNotAllowed.new(e.params)
50
44
  end
45
+
46
+ def verify_pagination_params
47
+ if @limit < 1
48
+ raise JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit)
49
+ elsif @limit > JSONAPI.configuration.maximum_page_size
50
+ raise JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit,
51
+ "Limit exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
52
+ end
53
+
54
+ if @offset < 0
55
+ raise JSONAPI::Exceptions::InvalidPageValue.new(:offset, @offset)
56
+ end
57
+ end
51
58
  end
52
59
 
53
60
  class PagedPaginator < JSONAPI::Paginator
54
61
  def initialize(params)
55
62
  parse_pagination_params(params)
63
+ verify_pagination_params
56
64
  end
57
65
 
58
- def apply(relation)
66
+ def apply(relation, order_options)
59
67
  offset = (@number - 1) * @size
60
68
  relation.offset(offset).limit(@size)
61
69
  end
@@ -70,13 +78,6 @@ class PagedPaginator < JSONAPI::Paginator
70
78
 
71
79
  @size = validparams[:size] ? validparams[:size].to_i : JSONAPI.configuration.default_page_size
72
80
  @number = validparams[:number] ? validparams[:number].to_i : 1
73
-
74
- if @size < 1
75
- raise JSONAPI::Exceptions::InvalidPageValue.new(:size, validparams[:size])
76
- elsif @size > JSONAPI.configuration.maximum_page_size
77
- raise JSONAPI::Exceptions::InvalidPageValue.new(:size, validparams[:size],
78
- "size exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
79
- end
80
81
  else
81
82
  @size = JSONAPI.configuration.default_page_size
82
83
  @number = params.to_i
@@ -84,4 +85,17 @@ class PagedPaginator < JSONAPI::Paginator
84
85
  rescue ActionController::UnpermittedParameters => e
85
86
  raise JSONAPI::Exceptions::PageParametersNotAllowed.new(e.params)
86
87
  end
88
+
89
+ def verify_pagination_params
90
+ if @size < 1
91
+ raise JSONAPI::Exceptions::InvalidPageValue.new(:size, @size)
92
+ elsif @size > JSONAPI.configuration.maximum_page_size
93
+ raise JSONAPI::Exceptions::InvalidPageValue.new(:size, @size,
94
+ "size exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
95
+ end
96
+
97
+ if @number < 1
98
+ raise JSONAPI::Exceptions::InvalidPageValue.new(:number, @number)
99
+ end
100
+ end
87
101
  end
@@ -4,71 +4,121 @@ require 'jsonapi/paginator'
4
4
  module JSONAPI
5
5
  class Request
6
6
  attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :operations,
7
- :resource_klass, :context, :paginator, :source_klass, :source_id
7
+ :resource_klass, :context, :paginator, :source_klass, :source_id,
8
+ :include_directives
8
9
 
9
10
  def initialize(params = nil, options = {})
10
- @context = options.fetch(:context, nil)
11
+ @context = options[:context]
11
12
  @key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
12
13
  @errors = []
13
14
  @operations = []
14
15
  @fields = {}
15
- @include = []
16
16
  @filters = {}
17
- @sort_criteria = []
17
+ @sort_criteria = [{field: 'id', direction: :asc}]
18
18
  @source_klass = nil
19
19
  @source_id = nil
20
+ @include_directives = nil
21
+ @paginator = nil
22
+ @id = nil
20
23
 
21
- setup(params) if params
24
+ setup_action(params)
22
25
  end
23
26
 
24
- def setup(params)
27
+ def setup_action(params)
28
+ return if params.nil?
29
+
25
30
  @resource_klass ||= Resource.resource_for(params[:controller]) if params[:controller]
26
31
 
27
32
  unless params.nil?
28
- case params[:action]
29
- when 'index'
30
- parse_fields(params[:fields])
31
- parse_include(params[:include])
32
- parse_filters(params[:filter])
33
- parse_sort_criteria(params[:sort])
34
- parse_pagination(params[:page])
35
- when 'get_related_resource', 'get_related_resources'
36
- @source_klass = Resource.resource_for(params.require(:source))
37
- @source_id = @source_klass.verify_key(params.require(@source_klass._as_parent_key), @context)
38
- parse_fields(params[:fields])
39
- parse_include(params[:include])
40
- parse_filters(params[:filter])
41
- parse_sort_criteria(params[:sort])
42
- parse_pagination(params[:page])
43
- when 'show'
44
- parse_fields(params[:fields])
45
- parse_include(params[:include])
46
- when 'create'
47
- parse_fields(params[:fields])
48
- parse_include(params[:include])
49
- parse_add_operation(params.require(:data))
50
- when 'create_association'
51
- parse_add_association_operation(params.require(:data),
52
- params.require(:association),
53
- params.require(@resource_klass._as_parent_key))
54
- when 'update_association'
55
- parse_update_association_operation(params.fetch(:data),
56
- params.require(:association),
57
- params.require(@resource_klass._as_parent_key))
58
- when 'update'
59
- parse_fields(params[:fields])
60
- parse_include(params[:include])
61
- parse_replace_operation(params.require(:data), params.require(@resource_klass._primary_key))
62
- when 'destroy'
63
- parse_remove_operation(params)
64
- when 'destroy_association'
65
- parse_remove_association_operation(params)
33
+ setup_action_method_name = "setup_#{params[:action]}_action"
34
+ if respond_to?(setup_action_method_name)
35
+ send(setup_action_method_name, params)
66
36
  end
67
37
  end
68
38
  rescue ActionController::ParameterMissing => e
69
39
  @errors.concat(JSONAPI::Exceptions::ParameterMissing.new(e.param).errors)
70
40
  end
71
41
 
42
+ def setup_index_action(params)
43
+ parse_fields(params[:fields])
44
+ parse_include_directives(params[:include])
45
+ set_default_filters
46
+ parse_filters(params[:filter])
47
+ parse_sort_criteria(params[:sort])
48
+ parse_pagination(params[:page])
49
+ add_find_operation
50
+ end
51
+
52
+ def setup_get_related_resource_action(params)
53
+ initialize_source(params)
54
+ parse_fields(params[:fields])
55
+ parse_include_directives(params[:include])
56
+ set_default_filters
57
+ parse_filters(params[:filter])
58
+ parse_sort_criteria(params[:sort])
59
+ parse_pagination(params[:page])
60
+ add_show_related_resource_operation(params[:association])
61
+ end
62
+
63
+ def setup_get_related_resources_action(params)
64
+ initialize_source(params)
65
+ parse_fields(params[:fields])
66
+ parse_include_directives(params[:include])
67
+ set_default_filters
68
+ parse_filters(params[:filter])
69
+ parse_sort_criteria(params[:sort])
70
+ parse_pagination(params[:page])
71
+ add_show_related_resources_operation(params[:association])
72
+ end
73
+
74
+ def setup_show_action(params)
75
+ parse_fields(params[:fields])
76
+ parse_include_directives(params[:include])
77
+ @id = params[:id]
78
+ add_show_operation
79
+ end
80
+
81
+ def setup_show_association_action(params)
82
+ add_show_association_operation(params[:association], params.require(@resource_klass._as_parent_key))
83
+ end
84
+
85
+ def setup_create_action(params)
86
+ parse_fields(params[:fields])
87
+ parse_include_directives(params[:include])
88
+ parse_add_operation(params.require(:data))
89
+ end
90
+
91
+ def setup_create_association_action(params)
92
+ parse_add_association_operation(params.require(:data),
93
+ params.require(:association),
94
+ params.require(@resource_klass._as_parent_key))
95
+ end
96
+
97
+ def setup_update_association_action(params)
98
+ parse_update_association_operation(params.fetch(:data),
99
+ params.require(:association),
100
+ params.require(@resource_klass._as_parent_key))
101
+ end
102
+
103
+ def setup_update_action(params)
104
+ parse_fields(params[:fields])
105
+ parse_include_directives(params[:include])
106
+ parse_replace_operation(params.require(:data), params.require(:id))
107
+ end
108
+
109
+ def setup_destroy_action(params)
110
+ parse_remove_operation(params)
111
+ end
112
+
113
+ def setup_destroy_association_action (params)
114
+ parse_remove_association_operation(params)
115
+ end
116
+
117
+ def initialize_source(params)
118
+ @source_klass = Resource.resource_for(params.require(:source))
119
+ @source_id = @source_klass.verify_key(params.require(@source_klass._as_parent_key), @context)
120
+ end
121
+
72
122
  def parse_pagination(page)
73
123
  paginator_name = @resource_klass._paginator
74
124
  @paginator = JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none
@@ -142,22 +192,29 @@ module JSONAPI
142
192
  end
143
193
  end
144
194
 
145
- def parse_include(include)
195
+ def parse_include_directives(include)
146
196
  return if include.nil?
147
197
 
148
198
  included_resources = CSV.parse_line(include)
149
199
  return if included_resources.nil?
150
200
 
151
- @include = []
201
+ include = []
152
202
  included_resources.each do |included_resource|
153
203
  check_include(@resource_klass, included_resource.partition('.'))
154
- @include.push(unformat_key(included_resource).to_s)
204
+ include.push(unformat_key(included_resource).to_s)
155
205
  end
206
+
207
+ @include_directives = JSONAPI::IncludeDirectives.new(include)
156
208
  end
157
209
 
158
210
  def parse_filters(filters)
159
211
  return unless filters
160
- @filters = {}
212
+
213
+ unless filters.class.method_defined?(:each)
214
+ @errors.concat(JSONAPI::Exceptions::InvalidFiltersSyntax.new(filters).errors)
215
+ return
216
+ end
217
+
161
218
  filters.each do |key, value|
162
219
  filter = unformat_key(key)
163
220
  if @resource_klass._allowed_filter?(filter)
@@ -168,18 +225,23 @@ module JSONAPI
168
225
  end
169
226
  end
170
227
 
228
+ def set_default_filters
229
+ @resource_klass._allowed_filters.each do |filter, opts|
230
+ next if opts[:default].nil? || !@filters[filter].nil?
231
+ @filters[filter] = opts[:default]
232
+ end
233
+ end
234
+
171
235
  def parse_sort_criteria(sort_criteria)
172
236
  return unless sort_criteria
173
237
 
174
238
  @sort_criteria = CSV.parse_line(URI.unescape(sort_criteria)).collect do |sort|
175
- sort_criteria = {field: unformat_key(sort[1..-1]).to_s}
176
- if sort.start_with?('+', ' ')
177
- sort_criteria[:direction] = :asc
178
- elsif sort.start_with?('-')
239
+ if sort.start_with?('-')
240
+ sort_criteria = {field: unformat_key(sort[1..-1]).to_s}
179
241
  sort_criteria[:direction] = :desc
180
242
  else
181
- @errors.concat(JSONAPI::Exceptions::InvalidSortFormat
182
- .new(format_key(resource_klass._type), sort).errors)
243
+ sort_criteria = {field: unformat_key(sort).to_s}
244
+ sort_criteria[:direction] = :asc
183
245
  end
184
246
 
185
247
  check_sort_criteria(@resource_klass, sort_criteria)
@@ -197,27 +259,96 @@ module JSONAPI
197
259
  end
198
260
  end
199
261
 
262
+ def add_find_operation
263
+ @operations.push JSONAPI::FindOperation.new(
264
+ @resource_klass,
265
+ {
266
+ filters: @filters,
267
+ include_directives: @include_directives,
268
+ sort_criteria: @sort_criteria,
269
+ paginator: @paginator
270
+ }
271
+ )
272
+ end
273
+
274
+ def add_show_operation
275
+ @operations.push JSONAPI::ShowOperation.new(
276
+ @resource_klass,
277
+ {
278
+ id: @id,
279
+ include_directives: @include_directives
280
+ }
281
+ )
282
+ end
283
+
284
+ def add_show_association_operation(association_type, parent_key)
285
+ @operations.push JSONAPI::ShowAssociationOperation.new(
286
+ @resource_klass,
287
+ {
288
+ association_type: association_type,
289
+ parent_key: @resource_klass.verify_key(parent_key)
290
+ }
291
+ )
292
+ end
293
+
294
+ def add_show_related_resource_operation(association_type)
295
+ @operations.push JSONAPI::ShowRelatedResourceOperation.new(
296
+ @resource_klass,
297
+ {
298
+ association_type: association_type,
299
+ source_klass: @source_klass,
300
+ source_id: @source_id
301
+ }
302
+ )
303
+ end
304
+
305
+ def add_show_related_resources_operation(association_type)
306
+ @operations.push JSONAPI::ShowRelatedResourcesOperation.new(
307
+ @resource_klass,
308
+ {
309
+ association_type: association_type,
310
+ source_klass: @source_klass,
311
+ source_id: @source_id,
312
+ filters: @source_klass.verify_filters(@filters, @context),
313
+ sort_criteria: @sort_criteria,
314
+ paginator: @paginator
315
+ }
316
+ )
317
+ end
318
+
319
+ # TODO: Please remove after `createable_fields` is removed
320
+ # :nocov:
321
+ def creatable_fields
322
+ if @resource_klass.respond_to?(:createable_fields)
323
+ creatable_fields = @resource_klass.createable_fields(@context)
324
+ else
325
+ creatable_fields = @resource_klass.creatable_fields(@context)
326
+ end
327
+ end
328
+ # :nocov:
329
+
200
330
  def parse_add_operation(data)
201
- Array.wrap(data).each do |p|
202
- values = parse_params(verify_and_remove_type(p), @resource_klass.createable_fields(@context))
203
- @operations.push JSONAPI::CreateResourceOperation.new(@resource_klass, values)
331
+ Array.wrap(data).each do |params|
332
+ verify_type(params[:type])
333
+
334
+ data = parse_params(params, creatable_fields)
335
+ @operations.push JSONAPI::CreateResourceOperation.new(
336
+ @resource_klass,
337
+ {
338
+ data: data
339
+ }
340
+ )
204
341
  end
205
342
  rescue JSONAPI::Exceptions::Error => e
206
343
  @errors.concat(e.errors)
207
344
  end
208
345
 
209
- def verify_and_remove_type(params)
210
- #remove type and verify it matches the resource
211
- if unformat_key(params[:type]) == @resource_klass._type
212
- params.delete(:type)
213
- else
214
- if params[:type].nil?
215
- raise JSONAPI::Exceptions::ParameterMissing.new(:type)
216
- else
217
- raise JSONAPI::Exceptions::InvalidResource.new(params[:type])
218
- end
346
+ def verify_type(type)
347
+ if type.nil?
348
+ raise JSONAPI::Exceptions::ParameterMissing.new(:type)
349
+ elsif unformat_key(type).to_sym != @resource_klass._type
350
+ raise JSONAPI::Exceptions::InvalidResource.new(type)
219
351
  end
220
- params
221
352
  end
222
353
 
223
354
  def parse_has_one_links_object(raw)
@@ -264,65 +395,70 @@ module JSONAPI
264
395
  checked_has_many_associations = {}
265
396
 
266
397
  params.each do |key, value|
267
- if key == 'links' || key == :links
268
- value.each do |link_key, link_value|
269
- param = unformat_key(link_key)
270
-
271
- association = @resource_klass._association(param)
272
-
273
- if association.is_a?(JSONAPI::Association::HasOne)
274
- if link_value.nil?
275
- linkage = nil
276
- else
277
- linkage = link_value[:linkage]
278
- end
398
+ case key.to_s
399
+ when 'relationships'
400
+ value.each do |link_key, link_value|
401
+ param = unformat_key(link_key)
402
+
403
+ association = @resource_klass._association(param)
404
+
405
+ if association.is_a?(JSONAPI::Association::HasOne)
406
+ if link_value.nil?
407
+ linkage = nil
408
+ else
409
+ linkage = link_value[:data]
410
+ end
279
411
 
280
- links_object = parse_has_one_links_object(linkage)
281
- # Since we do not yet support polymorphic associations we will raise an error if the type does not match the
282
- # association's type.
283
- # ToDo: Support Polymorphic associations
284
- if links_object[:type] && (links_object[:type].to_s != association.type.to_s)
285
- raise JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
286
- end
412
+ links_object = parse_has_one_links_object(linkage)
413
+ # Since we do not yet support polymorphic associations we will raise an error if the type does not match the
414
+ # association's type.
415
+ # ToDo: Support Polymorphic associations
416
+ if links_object[:type] && (links_object[:type].to_s != association.type.to_s)
417
+ raise JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
418
+ end
287
419
 
288
- unless links_object[:id].nil?
289
- association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(links_object[:type]).to_s)
290
- checked_has_one_associations[param] = association_resource.verify_key(links_object[:id], @context)
291
- else
292
- checked_has_one_associations[param] = nil
293
- end
294
- elsif association.is_a?(JSONAPI::Association::HasMany)
295
- if link_value.is_a?(Array) && link_value.length == 0
296
- linkage = []
297
- elsif link_value.is_a?(Hash)
298
- linkage = link_value[:linkage]
299
- else
300
- raise JSONAPI::Exceptions::InvalidLinksObject.new
301
- end
420
+ unless links_object[:id].nil?
421
+ association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(links_object[:type]).to_s)
422
+ checked_has_one_associations[param] = association_resource.verify_key(links_object[:id], @context)
423
+ else
424
+ checked_has_one_associations[param] = nil
425
+ end
426
+ elsif association.is_a?(JSONAPI::Association::HasMany)
427
+ if link_value.is_a?(Array) && link_value.length == 0
428
+ linkage = []
429
+ elsif link_value.is_a?(Hash)
430
+ linkage = link_value[:data]
431
+ else
432
+ raise JSONAPI::Exceptions::InvalidLinksObject.new
433
+ end
302
434
 
303
- links_object = parse_has_many_links_object(linkage)
435
+ links_object = parse_has_many_links_object(linkage)
304
436
 
305
- # Since we do not yet support polymorphic associations we will raise an error if the type does not match the
306
- # association's type.
307
- # ToDo: Support Polymorphic associations
437
+ # Since we do not yet support polymorphic associations we will raise an error if the type does not match the
438
+ # association's type.
439
+ # ToDo: Support Polymorphic associations
308
440
 
309
- if links_object.length == 0
310
- checked_has_many_associations[param] = []
311
- else
312
- if links_object.length > 1 || !links_object.has_key?(unformat_key(association.type).to_s)
313
- raise JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
314
- end
441
+ if links_object.length == 0
442
+ checked_has_many_associations[param] = []
443
+ else
444
+ if links_object.length > 1 || !links_object.has_key?(unformat_key(association.type).to_s)
445
+ raise JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
446
+ end
315
447
 
316
- links_object.each_pair do |type, keys|
317
- association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(type).to_s)
318
- checked_has_many_associations[param] = association_resource.verify_keys(keys, @context)
448
+ links_object.each_pair do |type, keys|
449
+ association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(type).to_s)
450
+ checked_has_many_associations[param] = association_resource.verify_keys(keys, @context)
451
+ end
319
452
  end
320
453
  end
321
454
  end
322
- end
323
- else
324
- param = unformat_key(key)
325
- checked_attributes[param] = unformat_value(param, value)
455
+ when 'id'
456
+ checked_attributes['id'] = unformat_value(:id, value)
457
+ when 'attributes'
458
+ value.each do |key, value|
459
+ param = unformat_key(key)
460
+ checked_attributes[param] = unformat_value(param, value)
461
+ end
326
462
  end
327
463
  end
328
464
 
@@ -335,35 +471,58 @@ module JSONAPI
335
471
 
336
472
  def unformat_value(attribute, value)
337
473
  value_formatter = JSONAPI::ValueFormatter.value_formatter_for(@resource_klass._attribute_options(attribute)[:format])
338
- value_formatter.unformat(value, @context)
474
+ value_formatter.unformat(value)
339
475
  end
340
476
 
341
477
  def verify_permitted_params(params, allowed_fields)
342
478
  formatted_allowed_fields = allowed_fields.collect { |field| format_key(field).to_sym }
343
479
  params_not_allowed = []
480
+
344
481
  params.each do |key, value|
345
- if key == 'links' || key == :links
346
- value.each_key do |links_key|
347
- params_not_allowed.push(links_key) unless formatted_allowed_fields.include?(links_key.to_sym)
348
- end
349
- else
350
- params_not_allowed.push(key) unless formatted_allowed_fields.include?(key.to_sym)
482
+ case key.to_s
483
+ when 'relationships'
484
+ value.each_key do |links_key|
485
+ params_not_allowed.push(links_key) unless formatted_allowed_fields.include?(links_key.to_sym)
486
+ end
487
+ when 'attributes'
488
+ value.each do |attr_key, attr_value|
489
+ params_not_allowed.push(attr_key) unless formatted_allowed_fields.include?(attr_key.to_sym)
490
+ end
491
+ when 'type', 'id'
492
+ else
493
+ params_not_allowed.push(key)
351
494
  end
352
495
  end
496
+
353
497
  raise JSONAPI::Exceptions::ParametersNotAllowed.new(params_not_allowed) if params_not_allowed.length > 0
354
498
  end
355
499
 
500
+ # TODO: Please remove after `updateable_fields` is removed
501
+ # :nocov:
502
+ def updatable_fields
503
+ if @resource_klass.respond_to?(:updateable_fields)
504
+ @resource_klass.updateable_fields(@context)
505
+ else
506
+ @resource_klass.updatable_fields(@context)
507
+ end
508
+ end
509
+ # :nocov:
510
+
356
511
  def parse_add_association_operation(data, association_type, parent_key)
357
512
  association = resource_klass._association(association_type)
358
513
 
359
514
  if association.is_a?(JSONAPI::Association::HasMany)
360
- object_params = {links: {association.name => {linkage: data}}}
361
- verified_param_set = parse_params(object_params, @resource_klass.updateable_fields(@context))
362
-
363
- @operations.push JSONAPI::CreateHasManyAssociationOperation.new(resource_klass,
364
- parent_key,
365
- association_type,
366
- verified_param_set[:has_many].values[0])
515
+ object_params = {relationships: {format_key(association.name) => {data: data}}}
516
+ verified_param_set = parse_params(object_params, updatable_fields)
517
+
518
+ @operations.push JSONAPI::CreateHasManyAssociationOperation.new(
519
+ resource_klass,
520
+ {
521
+ resource_id: parent_key,
522
+ association_type: association_type,
523
+ data: verified_param_set[:has_many].values[0]
524
+ }
525
+ )
367
526
  end
368
527
  end
369
528
 
@@ -371,31 +530,38 @@ module JSONAPI
371
530
  association = resource_klass._association(association_type)
372
531
 
373
532
  if association.is_a?(JSONAPI::Association::HasOne)
374
- object_params = {links: {association.name => {linkage: data}}}
375
-
376
- verified_param_set = parse_params(object_params, @resource_klass.updateable_fields(@context))
377
-
378
- @operations.push JSONAPI::ReplaceHasOneAssociationOperation.new(resource_klass,
379
- parent_key,
380
- association_type,
381
- verified_param_set[:has_one].values[0])
533
+ object_params = {relationships: {format_key(association.name) => {data: data}}}
534
+ verified_param_set = parse_params(object_params, updatable_fields)
535
+
536
+ @operations.push JSONAPI::ReplaceHasOneAssociationOperation.new(
537
+ resource_klass,
538
+ {
539
+ resource_id: parent_key,
540
+ association_type: association_type,
541
+ key_value: verified_param_set[:has_one].values[0]
542
+ }
543
+ )
382
544
  else
383
545
  unless association.acts_as_set
384
546
  raise JSONAPI::Exceptions::HasManySetReplacementForbidden.new
385
547
  end
386
548
 
387
- object_params = {links: {association.name => {linkage: data}}}
388
- verified_param_set = parse_params(object_params, @resource_klass.updateable_fields(@context))
389
-
390
- @operations.push JSONAPI::ReplaceHasManyAssociationOperation.new(resource_klass,
391
- parent_key,
392
- association_type,
393
- verified_param_set[:has_many].values[0])
549
+ object_params = {relationships: {format_key(association.name) => {data: data}}}
550
+ verified_param_set = parse_params(object_params, updatable_fields)
551
+
552
+ @operations.push JSONAPI::ReplaceHasManyAssociationOperation.new(
553
+ resource_klass,
554
+ {
555
+ resource_id: parent_key,
556
+ association_type: association_type,
557
+ data: verified_param_set[:has_many].values[0]
558
+ }
559
+ )
394
560
  end
395
561
  end
396
562
 
397
563
  def parse_single_replace_operation(data, keys)
398
- if data[@resource_klass._primary_key].nil?
564
+ if data[:id].nil?
399
565
  raise JSONAPI::Exceptions::MissingKey.new
400
566
  end
401
567
 
@@ -404,19 +570,24 @@ module JSONAPI
404
570
  raise JSONAPI::Exceptions::ParameterMissing.new(:type)
405
571
  end
406
572
 
407
- key = data[@resource_klass._primary_key]
573
+ key = data[:id]
408
574
  if !keys.include?(key)
409
575
  raise JSONAPI::Exceptions::KeyNotIncludedInURL.new(key)
410
576
  end
411
577
 
412
- if !keys.include?(@resource_klass._primary_key)
578
+ if !keys.include?(:id)
413
579
  data.delete(:id)
414
580
  end
415
581
 
416
- @operations.push(JSONAPI::ReplaceFieldsOperation.new(@resource_klass,
417
- key,
418
- parse_params(verify_and_remove_type(data),
419
- @resource_klass.updateable_fields(@context))))
582
+ verify_type(data[:type])
583
+
584
+ @operations.push JSONAPI::ReplaceFieldsOperation.new(
585
+ @resource_klass,
586
+ {
587
+ resource_id: key,
588
+ data: parse_params(data, updatable_fields)
589
+ }
590
+ )
420
591
  end
421
592
 
422
593
  def parse_replace_operation(data, keys)
@@ -437,10 +608,15 @@ module JSONAPI
437
608
  end
438
609
 
439
610
  def parse_remove_operation(params)
440
- keys = parse_key_array(params.permit(@resource_klass._primary_key)[@resource_klass._primary_key])
611
+ keys = parse_key_array(params.permit(:id)[:id])
441
612
 
442
613
  keys.each do |key|
443
- @operations.push JSONAPI::RemoveResourceOperation.new(@resource_klass, key)
614
+ @operations.push JSONAPI::RemoveResourceOperation.new(
615
+ @resource_klass,
616
+ {
617
+ resource_id: key
618
+ }
619
+ )
444
620
  end
445
621
  rescue ActionController::UnpermittedParameters => e
446
622
  @errors.concat(JSONAPI::Exceptions::ParametersNotAllowed.new(e.params).errors)
@@ -457,15 +633,23 @@ module JSONAPI
457
633
  if association.is_a?(JSONAPI::Association::HasMany)
458
634
  keys = parse_key_array(params[:keys])
459
635
  keys.each do |key|
460
- @operations.push JSONAPI::RemoveHasManyAssociationOperation.new(resource_klass,
461
- parent_key,
462
- association_type,
463
- key)
636
+ @operations.push JSONAPI::RemoveHasManyAssociationOperation.new(
637
+ resource_klass,
638
+ {
639
+ resource_id: parent_key,
640
+ association_type: association_type,
641
+ associated_key: key
642
+ }
643
+ )
464
644
  end
465
645
  else
466
- @operations.push JSONAPI::RemoveHasOneAssociationOperation.new(resource_klass,
467
- parent_key,
468
- association_type)
646
+ @operations.push JSONAPI::RemoveHasOneAssociationOperation.new(
647
+ resource_klass,
648
+ {
649
+ resource_id: parent_key,
650
+ association_type: association_type
651
+ }
652
+ )
469
653
  end
470
654
  end
471
655