jsonapi-resources 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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)