jsonapi-resources 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/README.md +103 -71
  4. data/Rakefile +2 -2
  5. data/jsonapi-resources.gemspec +2 -2
  6. data/lib/jsonapi-resources.rb +0 -1
  7. data/lib/jsonapi/active_record_operations_processor.rb +10 -2
  8. data/lib/jsonapi/acts_as_resource_controller.rb +26 -24
  9. data/lib/jsonapi/association.rb +50 -15
  10. data/lib/jsonapi/callbacks.rb +1 -2
  11. data/lib/jsonapi/configuration.rb +8 -24
  12. data/lib/jsonapi/error.rb +1 -2
  13. data/lib/jsonapi/error_codes.rb +3 -1
  14. data/lib/jsonapi/exceptions.rb +59 -47
  15. data/lib/jsonapi/include_directives.rb +11 -11
  16. data/lib/jsonapi/mime_types.rb +2 -2
  17. data/lib/jsonapi/operation.rb +28 -11
  18. data/lib/jsonapi/operations_processor.rb +16 -5
  19. data/lib/jsonapi/paginator.rb +19 -19
  20. data/lib/jsonapi/request.rb +175 -196
  21. data/lib/jsonapi/resource.rb +158 -105
  22. data/lib/jsonapi/resource_serializer.rb +37 -26
  23. data/lib/jsonapi/resources/version.rb +2 -2
  24. data/lib/jsonapi/response_document.rb +5 -4
  25. data/lib/jsonapi/routing_ext.rb +24 -19
  26. data/test/controllers/controller_test.rb +261 -31
  27. data/test/fixtures/active_record.rb +206 -8
  28. data/test/fixtures/book_comments.yml +2 -1
  29. data/test/fixtures/books.yml +1 -0
  30. data/test/fixtures/documents.yml +3 -0
  31. data/test/fixtures/people.yml +8 -1
  32. data/test/fixtures/pictures.yml +15 -0
  33. data/test/fixtures/products.yml +3 -0
  34. data/test/fixtures/vehicles.yml +8 -0
  35. data/test/helpers/{hash_helpers.rb → assertions.rb} +6 -1
  36. data/test/integration/requests/request_test.rb +14 -3
  37. data/test/integration/routes/routes_test.rb +47 -0
  38. data/test/test_helper.rb +27 -4
  39. data/test/unit/serializer/include_directives_test.rb +5 -0
  40. data/test/unit/serializer/polymorphic_serializer_test.rb +384 -0
  41. data/test/unit/serializer/serializer_test.rb +19 -1
  42. metadata +14 -4
@@ -1,18 +1,17 @@
1
1
  module JSONAPI
2
2
  class IncludeDirectives
3
-
4
3
  # Construct an IncludeDirectives Hash from an array of dot separated include strings.
5
4
  # For example ['posts.comments.tags']
6
5
  # will transform into =>
7
6
  # {
8
- # :posts=>{
9
- # :include=>true,
10
- # :include_related=>{
11
- # :comments=>{
12
- # :include=>true,
13
- # :include_related=>{
14
- # :tags=>{
15
- # :include=>true
7
+ # posts:{
8
+ # include:true,
9
+ # include_related:{
10
+ # comments:{
11
+ # include:true,
12
+ # include_related:{
13
+ # tags:{
14
+ # include:true
16
15
  # }
17
16
  # }
18
17
  # }
@@ -21,7 +20,7 @@ module JSONAPI
21
20
  # }
22
21
 
23
22
  def initialize(includes_array)
24
- @include_directives_hash = {include_related: {}}
23
+ @include_directives_hash = { include_related: {} }
25
24
  includes_array.each do |include|
26
25
  parse_include(include)
27
26
  end
@@ -36,11 +35,12 @@ module JSONAPI
36
35
  end
37
36
 
38
37
  private
38
+
39
39
  def get_related(current_path)
40
40
  current = @include_directives_hash
41
41
  current_path.split('.').each do |fragment|
42
42
  fragment = fragment.to_sym
43
- current[:include_related][fragment] ||= {include: false, include_related: {}}
43
+ current[:include_related][fragment] ||= { include: false, include_related: {} }
44
44
  current = current[:include_related][fragment]
45
45
  end
46
46
  current
@@ -1,9 +1,9 @@
1
1
  module JSONAPI
2
- MEDIA_TYPE = "application/vnd.api+json"
2
+ MEDIA_TYPE = 'application/vnd.api+json'
3
3
  end
4
4
 
5
5
  Mime::Type.register JSONAPI::MEDIA_TYPE, :api_json
6
6
 
7
- ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup(JSONAPI::MEDIA_TYPE)]=lambda do |body|
7
+ ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup(JSONAPI::MEDIA_TYPE)] = lambda do |body|
8
8
  JSON.parse(body)
9
9
  end
@@ -27,8 +27,8 @@ module JSONAPI
27
27
 
28
28
  def record_count
29
29
  @_record_count ||= @resource_klass.find_count(@resource_klass.verify_filters(@filters, @context),
30
- context: @context,
31
- include_directives: @include_directives)
30
+ context: @context,
31
+ include_directives: @include_directives)
32
32
  end
33
33
 
34
34
  def pagination_params
@@ -43,10 +43,10 @@ module JSONAPI
43
43
 
44
44
  def apply
45
45
  resource_records = @resource_klass.find(@resource_klass.verify_filters(@filters, @context),
46
- context: @context,
47
- include_directives: @include_directives,
48
- sort_criteria: @sort_criteria,
49
- paginator: @paginator)
46
+ context: @context,
47
+ include_directives: @include_directives,
48
+ sort_criteria: @sort_criteria,
49
+ paginator: @paginator)
50
50
 
51
51
  options = {}
52
52
  if JSONAPI.configuration.top_level_links_include_pagination
@@ -153,11 +153,9 @@ module JSONAPI
153
153
  source_resource = @source_klass.find_by_key(@source_id, context: @context)
154
154
 
155
155
  related_resource = source_resource.send(@association_type,
156
- {
157
- filters: @filters,
158
- sort_criteria: @sort_criteria,
159
- paginator: @paginator
160
- })
156
+ filters: @filters,
157
+ sort_criteria: @sort_criteria,
158
+ paginator: @paginator)
161
159
 
162
160
  return JSONAPI::ResourceOperationResult.new(:ok, related_resource)
163
161
 
@@ -238,6 +236,25 @@ module JSONAPI
238
236
  end
239
237
  end
240
238
 
239
+ class ReplacePolymorphicHasOneAssociationOperation < Operation
240
+ attr_reader :resource_id, :association_type, :key_value, :key_type
241
+
242
+ def initialize(resource_klass, options = {})
243
+ @resource_id = options.fetch(:resource_id)
244
+ @key_value = options.fetch(:key_value)
245
+ @key_type = options.fetch(:key_type)
246
+ @association_type = options.fetch(:association_type).to_sym
247
+ super(resource_klass, options)
248
+ end
249
+
250
+ def apply
251
+ resource = @resource_klass.find_by_key(@resource_id, context: @context)
252
+ result = resource.replace_polymorphic_has_one_link(@association_type, @key_value, @key_type)
253
+
254
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
255
+ end
256
+ end
257
+
241
258
  class CreateHasManyAssociationOperation < Operation
242
259
  attr_reader :resource_id, :association_type, :data
243
260
 
@@ -12,6 +12,7 @@ module JSONAPI
12
12
  :remove_resource_operation,
13
13
  :replace_fields_operation,
14
14
  :replace_has_one_association_operation,
15
+ :replace_polymorphic_has_one_association_operation,
15
16
  :create_has_many_association_operation,
16
17
  :replace_has_many_association_operation,
17
18
  :remove_has_many_association_operation,
@@ -34,7 +35,7 @@ module JSONAPI
34
35
  @transactional = false
35
36
  if @operations.length > 1
36
37
  @operations.each do |operation|
37
- @transactional = @transactional | operation.transactional
38
+ @transactional |= operation.transactional
38
39
  end
39
40
  end
40
41
 
@@ -56,9 +57,7 @@ module JSONAPI
56
57
  @result.meta.merge!(@operation_meta)
57
58
  @result.links.merge!(@operation_links)
58
59
  @results.add_result(@result)
59
- if @results.has_errors?
60
- rollback
61
- end
60
+ rollback if @results.has_errors?
62
61
  end
63
62
  end
64
63
  @results.meta = @operations_meta
@@ -82,9 +81,21 @@ module JSONAPI
82
81
 
83
82
  def process_operation(operation)
84
83
  operation.apply
84
+
85
+ rescue JSONAPI::Exceptions::Error => e
86
+ # :nocov:
87
+ raise e
88
+ # :nocov:
89
+
90
+ rescue => e
91
+ # :nocov:
92
+ internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
93
+ Rails.logger.error { "Internal Server Error: #{e.message} #{e.backtrace.join("\n")}" }
94
+ return JSONAPI::ErrorsOperationResult.new(internal_server_error.errors[0].code, internal_server_error.errors)
95
+ # :nocov:
85
96
  end
86
97
  end
87
98
  end
88
99
 
89
100
  class BasicOperationsProcessor < JSONAPI::OperationsProcessor
90
- end
101
+ end
@@ -1,13 +1,13 @@
1
1
  module JSONAPI
2
2
  class Paginator
3
- def initialize(params)
3
+ def initialize(_params)
4
4
  end
5
5
 
6
- def apply(relation, order_options)
6
+ def apply(_relation, _order_options)
7
7
  # relation
8
8
  end
9
9
 
10
- def links_page_params(options = {})
10
+ def links_page_params(_options = {})
11
11
  # :nocov:
12
12
  {}
13
13
  # :nocov:
@@ -40,7 +40,7 @@ class OffsetPaginator < JSONAPI::Paginator
40
40
  true
41
41
  end
42
42
 
43
- def apply(relation, order_options)
43
+ def apply(relation, _order_options)
44
44
  relation.offset(@offset).limit(@limit)
45
45
  end
46
46
 
@@ -56,9 +56,7 @@ class OffsetPaginator < JSONAPI::Paginator
56
56
  if @offset > 0
57
57
  previous_offset = @offset - @limit
58
58
 
59
- if previous_offset < 0
60
- previous_offset = 0
61
- end
59
+ previous_offset = 0 if previous_offset < 0
62
60
 
63
61
  links_page_params['previous'] = {
64
62
  'offset' => previous_offset,
@@ -71,7 +69,7 @@ class OffsetPaginator < JSONAPI::Paginator
71
69
  unless next_offset >= record_count
72
70
  links_page_params['next'] = {
73
71
  'offset' => next_offset,
74
- 'limit'=> @limit
72
+ 'limit' => @limit
75
73
  }
76
74
  end
77
75
 
@@ -86,6 +84,7 @@ class OffsetPaginator < JSONAPI::Paginator
86
84
  end
87
85
 
88
86
  private
87
+
89
88
  def parse_pagination_params(params)
90
89
  if params.nil?
91
90
  @offset = 0
@@ -96,7 +95,7 @@ class OffsetPaginator < JSONAPI::Paginator
96
95
  @offset = validparams[:offset] ? validparams[:offset].to_i : 0
97
96
  @limit = validparams[:limit] ? validparams[:limit].to_i : JSONAPI.configuration.default_page_size
98
97
  else
99
- raise JSONAPI::Exceptions::InvalidPageObject.new
98
+ fail JSONAPI::Exceptions::InvalidPageObject.new
100
99
  end
101
100
  rescue ActionController::UnpermittedParameters => e
102
101
  raise JSONAPI::Exceptions::PageParametersNotAllowed.new(e.params)
@@ -104,14 +103,14 @@ class OffsetPaginator < JSONAPI::Paginator
104
103
 
105
104
  def verify_pagination_params
106
105
  if @limit < 1
107
- raise JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit)
106
+ fail JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit)
108
107
  elsif @limit > JSONAPI.configuration.maximum_page_size
109
- raise JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit,
110
- "Limit exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
108
+ fail JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit,
109
+ "Limit exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
111
110
  end
112
111
 
113
112
  if @offset < 0
114
- raise JSONAPI::Exceptions::InvalidPageValue.new(:offset, @offset)
113
+ fail JSONAPI::Exceptions::InvalidPageValue.new(:offset, @offset)
115
114
  end
116
115
  end
117
116
  end
@@ -128,7 +127,7 @@ class PagedPaginator < JSONAPI::Paginator
128
127
  true
129
128
  end
130
129
 
131
- def apply(relation, order_options)
130
+ def apply(relation, _order_options)
132
131
  offset = (@number - 1) * @size
133
132
  relation.offset(offset).limit(@size)
134
133
  end
@@ -154,7 +153,7 @@ class PagedPaginator < JSONAPI::Paginator
154
153
  unless @number >= page_count
155
154
  links_page_params['next'] = {
156
155
  'number' => @number + 1,
157
- 'size'=> @size
156
+ 'size' => @size
158
157
  }
159
158
  end
160
159
 
@@ -169,6 +168,7 @@ class PagedPaginator < JSONAPI::Paginator
169
168
  end
170
169
 
171
170
  private
171
+
172
172
  def parse_pagination_params(params)
173
173
  if params.nil?
174
174
  @number = 1
@@ -188,14 +188,14 @@ class PagedPaginator < JSONAPI::Paginator
188
188
 
189
189
  def verify_pagination_params
190
190
  if @size < 1
191
- raise JSONAPI::Exceptions::InvalidPageValue.new(:size, @size)
191
+ fail JSONAPI::Exceptions::InvalidPageValue.new(:size, @size)
192
192
  elsif @size > JSONAPI.configuration.maximum_page_size
193
- raise JSONAPI::Exceptions::InvalidPageValue.new(:size, @size,
194
- "size exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
193
+ fail JSONAPI::Exceptions::InvalidPageValue.new(:size, @size,
194
+ "size exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
195
195
  end
196
196
 
197
197
  if @number < 1
198
- raise JSONAPI::Exceptions::InvalidPageValue.new(:number, @number)
198
+ fail JSONAPI::Exceptions::InvalidPageValue.new(:number, @number)
199
199
  end
200
200
  end
201
201
  end
@@ -15,7 +15,7 @@ module JSONAPI
15
15
  @operations = []
16
16
  @fields = {}
17
17
  @filters = {}
18
- @sort_criteria = [{field: 'id', direction: :asc}]
18
+ @sort_criteria = [{ field: 'id', direction: :asc }]
19
19
  @source_klass = nil
20
20
  @source_id = nil
21
21
  @include_directives = nil
@@ -111,7 +111,7 @@ module JSONAPI
111
111
  parse_remove_operation(params)
112
112
  end
113
113
 
114
- def setup_destroy_association_action (params)
114
+ def setup_destroy_association_action(params)
115
115
  parse_remove_association_operation(params)
116
116
  end
117
117
 
@@ -139,7 +139,7 @@ module JSONAPI
139
139
  extracted_fields[field] = resource_fields
140
140
  end
141
141
  else
142
- raise JSONAPI::Exceptions::InvalidFieldFormat.new
142
+ fail JSONAPI::Exceptions::InvalidFieldFormat.new
143
143
  end
144
144
 
145
145
  # Validate the fields
@@ -148,13 +148,13 @@ module JSONAPI
148
148
  extracted_fields[type] = []
149
149
  begin
150
150
  if type != format_key(type)
151
- raise JSONAPI::Exceptions::InvalidResource.new(type)
151
+ fail JSONAPI::Exceptions::InvalidResource.new(type)
152
152
  end
153
153
  type_resource = Resource.resource_for(@resource_klass.module_path + underscored_type.to_s)
154
154
  rescue NameError
155
155
  @errors.concat(JSONAPI::Exceptions::InvalidResource.new(type).errors)
156
156
  rescue JSONAPI::Exceptions::InvalidResource => e
157
- @errors.concat(e.errors)
157
+ @errors.concat(e.errors)
158
158
  end
159
159
 
160
160
  if type_resource.nil? || !(@resource_klass._type == underscored_type ||
@@ -185,11 +185,11 @@ module JSONAPI
185
185
  association = resource_klass._association(association_name)
186
186
  if association && format_key(association_name) == include_parts.first
187
187
  unless include_parts.last.empty?
188
- check_include(Resource.resource_for(@resource_klass.module_path + association.class_name.to_s), include_parts.last.partition('.'))
188
+ check_include(Resource.resource_for(@resource_klass.module_path + association.class_name.to_s.underscore), include_parts.last.partition('.'))
189
189
  end
190
190
  else
191
191
  @errors.concat(JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type),
192
- include_parts.first,).errors)
192
+ include_parts.first).errors)
193
193
  end
194
194
  end
195
195
 
@@ -238,10 +238,10 @@ module JSONAPI
238
238
 
239
239
  @sort_criteria = CSV.parse_line(URI.unescape(sort_criteria)).collect do |sort|
240
240
  if sort.start_with?('-')
241
- sort_criteria = {field: unformat_key(sort[1..-1]).to_s}
241
+ sort_criteria = { field: unformat_key(sort[1..-1]).to_s }
242
242
  sort_criteria[:direction] = :desc
243
243
  else
244
- sort_criteria = {field: unformat_key(sort).to_s}
244
+ sort_criteria = { field: unformat_key(sort).to_s }
245
245
  sort_criteria[:direction] = :asc
246
246
  end
247
247
 
@@ -262,64 +262,54 @@ module JSONAPI
262
262
 
263
263
  def add_find_operation
264
264
  @operations.push JSONAPI::FindOperation.new(
265
- @resource_klass,
266
- {
267
- context: @context,
268
- filters: @filters,
269
- include_directives: @include_directives,
270
- sort_criteria: @sort_criteria,
271
- paginator: @paginator
272
- }
273
- )
265
+ @resource_klass,
266
+ context: @context,
267
+ filters: @filters,
268
+ include_directives: @include_directives,
269
+ sort_criteria: @sort_criteria,
270
+ paginator: @paginator
271
+ )
274
272
  end
275
273
 
276
274
  def add_show_operation
277
275
  @operations.push JSONAPI::ShowOperation.new(
278
- @resource_klass,
279
- {
280
- context: @context,
281
- id: @id,
282
- include_directives: @include_directives
283
- }
284
- )
276
+ @resource_klass,
277
+ context: @context,
278
+ id: @id,
279
+ include_directives: @include_directives
280
+ )
285
281
  end
286
282
 
287
283
  def add_show_association_operation(association_type, parent_key)
288
284
  @operations.push JSONAPI::ShowAssociationOperation.new(
289
- @resource_klass,
290
- {
291
- context: @context,
292
- association_type: association_type,
293
- parent_key: @resource_klass.verify_key(parent_key)
294
- }
295
- )
285
+ @resource_klass,
286
+ context: @context,
287
+ association_type: association_type,
288
+ parent_key: @resource_klass.verify_key(parent_key)
289
+ )
296
290
  end
297
291
 
298
292
  def add_show_related_resource_operation(association_type)
299
293
  @operations.push JSONAPI::ShowRelatedResourceOperation.new(
300
- @resource_klass,
301
- {
302
- context: @context,
303
- association_type: association_type,
304
- source_klass: @source_klass,
305
- source_id: @source_id
306
- }
307
- )
294
+ @resource_klass,
295
+ context: @context,
296
+ association_type: association_type,
297
+ source_klass: @source_klass,
298
+ source_id: @source_id
299
+ )
308
300
  end
309
301
 
310
302
  def add_show_related_resources_operation(association_type)
311
303
  @operations.push JSONAPI::ShowRelatedResourcesOperation.new(
312
- @resource_klass,
313
- {
314
- context: @context,
315
- association_type: association_type,
316
- source_klass: @source_klass,
317
- source_id: @source_id,
318
- filters: @source_klass.verify_filters(@filters, @context),
319
- sort_criteria: @sort_criteria,
320
- paginator: @paginator
321
- }
322
- )
304
+ @resource_klass,
305
+ context: @context,
306
+ association_type: association_type,
307
+ source_klass: @source_klass,
308
+ source_id: @source_id,
309
+ filters: @source_klass.verify_filters(@filters, @context),
310
+ sort_criteria: @sort_criteria,
311
+ paginator: @paginator
312
+ )
323
313
  end
324
314
 
325
315
  # TODO: Please remove after `createable_fields` is removed
@@ -339,12 +329,10 @@ module JSONAPI
339
329
 
340
330
  data = parse_params(params, creatable_fields)
341
331
  @operations.push JSONAPI::CreateResourceOperation.new(
342
- @resource_klass,
343
- {
344
- context: @context,
345
- data: data
346
- }
347
- )
332
+ @resource_klass,
333
+ context: @context,
334
+ data: data
335
+ )
348
336
  end
349
337
  rescue JSONAPI::Exceptions::Error => e
350
338
  @errors.concat(e.errors)
@@ -352,9 +340,9 @@ module JSONAPI
352
340
 
353
341
  def verify_type(type)
354
342
  if type.nil?
355
- raise JSONAPI::Exceptions::ParameterMissing.new(:type)
343
+ fail JSONAPI::Exceptions::ParameterMissing.new(:type)
356
344
  elsif unformat_key(type).to_sym != @resource_klass._type
357
- raise JSONAPI::Exceptions::InvalidResource.new(type)
345
+ fail JSONAPI::Exceptions::InvalidResource.new(type)
358
346
  end
359
347
  end
360
348
 
@@ -366,8 +354,8 @@ module JSONAPI
366
354
  }
367
355
  end
368
356
 
369
- if !raw.is_a?(Hash) || raw.length != 2 || !(raw.has_key?('type') && raw.has_key?('id'))
370
- raise JSONAPI::Exceptions::InvalidLinksObject.new
357
+ if !raw.is_a?(Hash) || raw.length != 2 || !(raw.key?('type') && raw.key?('id'))
358
+ fail JSONAPI::Exceptions::InvalidLinksObject.new
371
359
  end
372
360
 
373
361
  {
@@ -377,9 +365,7 @@ module JSONAPI
377
365
  end
378
366
 
379
367
  def parse_has_many_links_object(raw)
380
- if raw.nil?
381
- raise JSONAPI::Exceptions::InvalidLinksObject.new
382
- end
368
+ fail JSONAPI::Exceptions::InvalidLinksObject.new if raw.nil?
383
369
 
384
370
  links_object = {}
385
371
  if raw.is_a?(Array)
@@ -389,7 +375,7 @@ module JSONAPI
389
375
  links_object[link_object[:type]].push(link_object[:id])
390
376
  end
391
377
  else
392
- raise JSONAPI::Exceptions::InvalidLinksObject.new
378
+ fail JSONAPI::Exceptions::InvalidLinksObject.new
393
379
  end
394
380
  links_object
395
381
  end
@@ -403,69 +389,69 @@ module JSONAPI
403
389
 
404
390
  params.each do |key, value|
405
391
  case key.to_s
406
- when 'relationships'
407
- value.each do |link_key, link_value|
408
- param = unformat_key(link_key)
392
+ when 'relationships'
393
+ value.each do |link_key, link_value|
394
+ param = unformat_key(link_key)
395
+ association = @resource_klass._association(param)
396
+ if association.is_a?(JSONAPI::Association::HasOne)
397
+ if link_value.nil?
398
+ linkage = nil
399
+ else
400
+ linkage = link_value[:data]
401
+ end
409
402
 
410
- association = @resource_klass._association(param)
403
+ links_object = parse_has_one_links_object(linkage)
404
+ if !association.polymorphic? && links_object[:type] && (links_object[:type].to_s != association.type.to_s)
405
+ fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
406
+ end
411
407
 
412
- if association.is_a?(JSONAPI::Association::HasOne)
413
- if link_value.nil?
414
- linkage = nil
408
+ unless links_object[:id].nil?
409
+ association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(links_object[:type]).to_s)
410
+ association_id = association_resource.verify_key(links_object[:id], @context)
411
+ if association.polymorphic?
412
+ checked_has_one_associations[param] = { id: association_id, type: unformat_key(links_object[:type].to_s) }
415
413
  else
416
- linkage = link_value[:data]
417
- end
418
-
419
- links_object = parse_has_one_links_object(linkage)
420
- # Since we do not yet support polymorphic associations we will raise an error if the type does not match the
421
- # association's type.
422
- # ToDo: Support Polymorphic associations
423
- if links_object[:type] && (links_object[:type].to_s != association.type.to_s)
424
- raise JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
414
+ checked_has_one_associations[param] = association_id
425
415
  end
416
+ else
417
+ checked_has_one_associations[param] = nil
418
+ end
419
+ elsif association.is_a?(JSONAPI::Association::HasMany)
420
+ if link_value.is_a?(Array) && link_value.length == 0
421
+ linkage = []
422
+ elsif link_value.is_a?(Hash)
423
+ linkage = link_value[:data]
424
+ else
425
+ fail JSONAPI::Exceptions::InvalidLinksObject.new
426
+ end
426
427
 
427
- unless links_object[:id].nil?
428
- association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(links_object[:type]).to_s)
429
- checked_has_one_associations[param] = association_resource.verify_key(links_object[:id], @context)
430
- else
431
- checked_has_one_associations[param] = nil
432
- end
433
- elsif association.is_a?(JSONAPI::Association::HasMany)
434
- if link_value.is_a?(Array) && link_value.length == 0
435
- linkage = []
436
- elsif link_value.is_a?(Hash)
437
- linkage = link_value[:data]
438
- else
439
- raise JSONAPI::Exceptions::InvalidLinksObject.new
440
- end
428
+ links_object = parse_has_many_links_object(linkage)
441
429
 
442
- links_object = parse_has_many_links_object(linkage)
430
+ # Since we do not yet support polymorphic associations we will raise an error if the type does not match the
431
+ # association's type.
432
+ # ToDo: Support Polymorphic associations
443
433
 
444
- # Since we do not yet support polymorphic associations we will raise an error if the type does not match the
445
- # association's type.
446
- # ToDo: Support Polymorphic associations
434
+ if links_object.length == 0
435
+ checked_has_many_associations[param] = []
436
+ else
437
+ if links_object.length > 1 || !links_object.has_key?(unformat_key(association.type).to_s)
438
+ fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
439
+ end
447
440
 
448
- if links_object.length == 0
449
- checked_has_many_associations[param] = []
450
- else
451
- if links_object.length > 1 || !links_object.has_key?(unformat_key(association.type).to_s)
452
- raise JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
453
- end
454
-
455
- links_object.each_pair do |type, keys|
456
- association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(type).to_s)
457
- checked_has_many_associations[param] = association_resource.verify_keys(keys, @context)
458
- end
441
+ links_object.each_pair do |type, keys|
442
+ association_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(type).to_s)
443
+ checked_has_many_associations[param] = association_resource.verify_keys(keys, @context)
459
444
  end
460
445
  end
461
446
  end
462
- when 'id'
463
- checked_attributes['id'] = unformat_value(:id, value)
464
- when 'attributes'
465
- value.each do |key, value|
466
- param = unformat_key(key)
467
- checked_attributes[param] = unformat_value(param, value)
468
- end
447
+ end
448
+ when 'id'
449
+ checked_attributes['id'] = unformat_value(:id, value)
450
+ when 'attributes'
451
+ value.each do |key, value|
452
+ param = unformat_key(key)
453
+ checked_attributes[param] = unformat_value(param, value)
454
+ end
469
455
  end
470
456
  end
471
457
 
@@ -487,21 +473,21 @@ module JSONAPI
487
473
 
488
474
  params.each do |key, value|
489
475
  case key.to_s
490
- when 'relationships'
491
- value.each_key do |links_key|
492
- params_not_allowed.push(links_key) unless formatted_allowed_fields.include?(links_key.to_sym)
493
- end
494
- when 'attributes'
495
- value.each do |attr_key, attr_value|
496
- params_not_allowed.push(attr_key) unless formatted_allowed_fields.include?(attr_key.to_sym)
497
- end
498
- when 'type', 'id'
499
- else
500
- params_not_allowed.push(key)
476
+ when 'relationships'
477
+ value.each_key do |links_key|
478
+ params_not_allowed.push(links_key) unless formatted_allowed_fields.include?(links_key.to_sym)
479
+ end
480
+ when 'attributes'
481
+ value.each do |attr_key, _attr_value|
482
+ params_not_allowed.push(attr_key) unless formatted_allowed_fields.include?(attr_key.to_sym)
483
+ end
484
+ when 'type', 'id'
485
+ else
486
+ params_not_allowed.push(key)
501
487
  end
502
488
  end
503
489
 
504
- raise JSONAPI::Exceptions::ParametersNotAllowed.new(params_not_allowed) if params_not_allowed.length > 0
490
+ fail JSONAPI::Exceptions::ParametersNotAllowed.new(params_not_allowed) if params_not_allowed.length > 0
505
491
  end
506
492
 
507
493
  # TODO: Please remove after `updateable_fields` is removed
@@ -519,93 +505,92 @@ module JSONAPI
519
505
  association = resource_klass._association(association_type)
520
506
 
521
507
  if association.is_a?(JSONAPI::Association::HasMany)
522
- object_params = {relationships: {format_key(association.name) => {data: data}}}
508
+ object_params = { relationships: { format_key(association.name) => { data: data } } }
523
509
  verified_param_set = parse_params(object_params, updatable_fields)
524
510
 
525
511
  @operations.push JSONAPI::CreateHasManyAssociationOperation.new(
526
- resource_klass,
527
- {
528
- context: @context,
529
- resource_id: parent_key,
530
- association_type: association_type,
531
- data: verified_param_set[:has_many].values[0]
532
- }
533
- )
512
+ resource_klass,
513
+ context: @context,
514
+ resource_id: parent_key,
515
+ association_type: association_type,
516
+ data: verified_param_set[:has_many].values[0]
517
+ )
534
518
  end
535
519
  end
536
520
 
537
521
  def parse_update_association_operation(data, association_type, parent_key)
538
522
  association = resource_klass._association(association_type)
539
-
540
523
  if association.is_a?(JSONAPI::Association::HasOne)
541
- object_params = {relationships: {format_key(association.name) => {data: data}}}
542
- verified_param_set = parse_params(object_params, updatable_fields)
524
+ if association.polymorphic?
525
+ object_params = { relationships: { format_key(association.name) => { data: data } } }
526
+ verified_param_set = parse_params(object_params, updatable_fields)
543
527
 
544
- @operations.push JSONAPI::ReplaceHasOneAssociationOperation.new(
545
- resource_klass,
546
- {
528
+ @operations.push JSONAPI::ReplacePolymorphicHasOneAssociationOperation.new(
529
+ resource_klass,
530
+ context: @context,
531
+ resource_id: parent_key,
532
+ association_type: association_type,
533
+ key_value: verified_param_set[:has_one].values[0][:id],
534
+ key_type: verified_param_set[:has_one].values[0][:type]
535
+ )
536
+ else
537
+ object_params = { relationships: { format_key(association.name) => { data: data } } }
538
+ verified_param_set = parse_params(object_params, updatable_fields)
539
+
540
+ @operations.push JSONAPI::ReplaceHasOneAssociationOperation.new(
541
+ resource_klass,
547
542
  context: @context,
548
543
  resource_id: parent_key,
549
544
  association_type: association_type,
550
545
  key_value: verified_param_set[:has_one].values[0]
551
- }
552
- )
553
- else
546
+ )
547
+ end
548
+ elsif association.is_a?(JSONAPI::Association::HasMany)
554
549
  unless association.acts_as_set
555
- raise JSONAPI::Exceptions::HasManySetReplacementForbidden.new
550
+ fail JSONAPI::Exceptions::HasManySetReplacementForbidden.new
556
551
  end
557
552
 
558
- object_params = {relationships: {format_key(association.name) => {data: data}}}
553
+ object_params = { relationships: { format_key(association.name) => { data: data } } }
559
554
  verified_param_set = parse_params(object_params, updatable_fields)
560
555
 
561
556
  @operations.push JSONAPI::ReplaceHasManyAssociationOperation.new(
562
557
  resource_klass,
563
- {
564
- context: @context,
565
- resource_id: parent_key,
566
- association_type: association_type,
567
- data: verified_param_set[:has_many].values[0]
568
- }
558
+ context: @context,
559
+ resource_id: parent_key,
560
+ association_type: association_type,
561
+ data: verified_param_set[:has_many].values[0]
569
562
  )
570
563
  end
571
564
  end
572
565
 
573
566
  def parse_single_replace_operation(data, keys)
574
- if data[:id].nil?
575
- raise JSONAPI::Exceptions::MissingKey.new
576
- end
567
+ fail JSONAPI::Exceptions::MissingKey.new if data[:id].nil?
577
568
 
578
569
  type = data[:type]
579
570
  if type.nil? || type != format_key(@resource_klass._type).to_s
580
- raise JSONAPI::Exceptions::ParameterMissing.new(:type)
571
+ fail JSONAPI::Exceptions::ParameterMissing.new(:type)
581
572
  end
582
573
 
583
574
  key = data[:id]
584
- if !keys.include?(key)
585
- raise JSONAPI::Exceptions::KeyNotIncludedInURL.new(key)
575
+ unless keys.include?(key)
576
+ fail JSONAPI::Exceptions::KeyNotIncludedInURL.new(key)
586
577
  end
587
578
 
588
- if !keys.include?(:id)
589
- data.delete(:id)
590
- end
579
+ data.delete(:id) unless keys.include?(:id)
591
580
 
592
581
  verify_type(data[:type])
593
582
 
594
583
  @operations.push JSONAPI::ReplaceFieldsOperation.new(
595
- @resource_klass,
596
- {
597
- context: @context,
598
- resource_id: key,
599
- data: parse_params(data, updatable_fields)
600
- }
601
- )
584
+ @resource_klass,
585
+ context: @context,
586
+ resource_id: key,
587
+ data: parse_params(data, updatable_fields)
588
+ )
602
589
  end
603
590
 
604
591
  def parse_replace_operation(data, keys)
605
592
  if data.is_a?(Array)
606
- if keys.count != data.count
607
- raise JSONAPI::Exceptions::CountMismatch
608
- end
593
+ fail JSONAPI::Exceptions::CountMismatch if keys.count != data.count
609
594
 
610
595
  data.each do |object_params|
611
596
  parse_single_replace_operation(object_params, keys)
@@ -623,12 +608,10 @@ module JSONAPI
623
608
 
624
609
  keys.each do |key|
625
610
  @operations.push JSONAPI::RemoveResourceOperation.new(
626
- @resource_klass,
627
- {
628
- context: @context,
629
- resource_id: key
630
- }
631
- )
611
+ @resource_klass,
612
+ context: @context,
613
+ resource_id: key
614
+ )
632
615
  end
633
616
  rescue ActionController::UnpermittedParameters => e
634
617
  @errors.concat(JSONAPI::Exceptions::ParametersNotAllowed.new(e.params).errors)
@@ -646,29 +629,25 @@ module JSONAPI
646
629
  keys = parse_key_array(params[:keys])
647
630
  keys.each do |key|
648
631
  @operations.push JSONAPI::RemoveHasManyAssociationOperation.new(
649
- resource_klass,
650
- {
651
- context: @context,
652
- resource_id: parent_key,
653
- association_type: association_type,
654
- associated_key: key
655
- }
656
- )
632
+ resource_klass,
633
+ context: @context,
634
+ resource_id: parent_key,
635
+ association_type: association_type,
636
+ associated_key: key
637
+ )
657
638
  end
658
639
  else
659
640
  @operations.push JSONAPI::RemoveHasOneAssociationOperation.new(
660
- resource_klass,
661
- {
662
- context: @context,
663
- resource_id: parent_key,
664
- association_type: association_type
665
- }
666
- )
641
+ resource_klass,
642
+ context: @context,
643
+ resource_id: parent_key,
644
+ association_type: association_type
645
+ )
667
646
  end
668
647
  end
669
648
 
670
649
  def parse_key_array(raw)
671
- return @resource_klass.verify_keys(raw.split(/,/), context)
650
+ @resource_klass.verify_keys(raw.split(/,/), context)
672
651
  end
673
652
 
674
653
  def format_key(key)