jsonapi-resources 0.2.0 → 0.3.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.travis.yml +5 -2
  4. data/Gemfile +3 -1
  5. data/README.md +52 -13
  6. data/jsonapi-resources.gemspec +1 -1
  7. data/lib/jsonapi-resources.rb +1 -0
  8. data/lib/jsonapi/association.rb +1 -9
  9. data/lib/jsonapi/error_codes.rb +1 -0
  10. data/lib/jsonapi/exceptions.rb +9 -5
  11. data/lib/jsonapi/formatter.rb +9 -18
  12. data/lib/jsonapi/paginator.rb +4 -15
  13. data/lib/jsonapi/request.rb +26 -42
  14. data/lib/jsonapi/resource.rb +35 -45
  15. data/lib/jsonapi/resource_controller.rb +6 -32
  16. data/lib/jsonapi/resource_serializer.rb +62 -33
  17. data/lib/jsonapi/resources/version.rb +1 -1
  18. data/lib/jsonapi/routing_ext.rb +4 -4
  19. data/test/config/database.yml +2 -1
  20. data/test/controllers/controller_test.rb +200 -160
  21. data/test/fixtures/active_record.rb +44 -201
  22. data/test/fixtures/book_comments.yml +11 -0
  23. data/test/fixtures/books.yml +6 -0
  24. data/test/fixtures/comments.yml +17 -0
  25. data/test/fixtures/comments_tags.yml +20 -0
  26. data/test/fixtures/expense_entries.yml +13 -0
  27. data/test/fixtures/facts.yml +11 -0
  28. data/test/fixtures/iso_currencies.yml +17 -0
  29. data/test/fixtures/people.yml +24 -0
  30. data/test/fixtures/posts.yml +96 -0
  31. data/test/fixtures/posts_tags.yml +59 -0
  32. data/test/fixtures/preferences.yml +18 -0
  33. data/test/fixtures/sections.yml +8 -0
  34. data/test/fixtures/tags.yml +39 -0
  35. data/test/helpers/hash_helpers.rb +0 -7
  36. data/test/integration/requests/request_test.rb +86 -28
  37. data/test/integration/routes/routes_test.rb +14 -25
  38. data/test/test_helper.rb +41 -17
  39. data/test/unit/jsonapi_request/jsonapi_request_test.rb +152 -0
  40. data/test/unit/operation/operations_processor_test.rb +13 -2
  41. data/test/unit/resource/resource_test.rb +68 -13
  42. data/test/unit/serializer/serializer_test.rb +328 -220
  43. metadata +33 -6
  44. data/lib/jsonapi/resource_for.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7639392d6acfd2092e36563bebd32e791b7df37a
4
- data.tar.gz: 6c7e794b2646d60214746515810d9648566699e8
3
+ metadata.gz: 7979f6e3a6f09ec2999fb78b2e0a06aa8f6f8be4
4
+ data.tar.gz: 6819f84c905130eaf614059393c59e55de8c457c
5
5
  SHA512:
6
- metadata.gz: 0d53447be085f1ce717782ad9238116e30968e0380fe6e5612e3b5b1a680978285e363904c9c4a96b5ac115bcec8addad959851cd4dfeb58244ab704215eba1c
7
- data.tar.gz: 725dd95e1409afd23a1f22a6f7399a8fcc402734adc5da297cc019f65186c2777b3bb8c2aacf7955e1dcd1cc89af1fced6702898222ca2b3746be2e4800bbebc
6
+ metadata.gz: 7389937f7df1ce1ece2ee0a481e626c7c8385cdddda73c12ec7625192eea77d27c4daa9d3bc73a5a10a0eb6aeab8fa4944459b52d0e8fefa8d36469f84dcf230
7
+ data.tar.gz: 0e3c2330066fc109530a627b9d5126e9cb60365c7dd5727230df9f9f9de27b3c9089637de1d447677774d76784d9816107d6d06df3a411d67fc779d481c2c329
data/.gitignore CHANGED
@@ -17,4 +17,6 @@ test/tmp
17
17
  test/version_tmp
18
18
  tmp
19
19
  coverage
20
- test/log
20
+ test/log
21
+ test_db
22
+ test_db-journal
data/.travis.yml CHANGED
@@ -1,6 +1,9 @@
1
1
  language: ruby
2
+ env:
3
+ - "RAILS_VERSION=4.0"
4
+ - "RAILS_VERSION=4.1"
5
+ - "RAILS_VERSION=4.2"
2
6
  rvm:
3
- - 1.9.3
4
- - 2.0.0
7
+ - 2.0
5
8
  - 2.1
6
9
  - 2.2
data/Gemfile CHANGED
@@ -10,10 +10,12 @@ platforms :jruby do
10
10
  gem 'activerecord-jdbcsqlite3-adapter'
11
11
  end
12
12
 
13
- version = ENV['RAILS_VERSION'] || '4.0.4'
13
+ version = ENV['RAILS_VERSION'] || 'default'
14
14
  rails = case version
15
15
  when 'master'
16
16
  {:github => 'rails/rails'}
17
+ when 'default'
18
+ '>= 4.2'
17
19
  else
18
20
  "~> #{version}"
19
21
  end
data/README.md CHANGED
@@ -166,6 +166,7 @@ The system will lookup a value formatter named `DateWithTimezoneValueFormatter`
166
166
  #### Primary Key
167
167
 
168
168
  Resources are always represented using a key of `id`. If the underlying model does not use `id` as the primary key you can use the `primary_key` method to tell the resource which field on the model to use as the primary key. Note: this doesn't have to be the actual primary key of the model. For example you may wish to use integers internally and a different scheme publicly.
169
+ By default only integer values are allowed for primary key. To change this behavior you can override `verify_key` class method:
169
170
 
170
171
  ```ruby
171
172
  class CurrencyResource < JSONAPI::Resource
@@ -173,8 +174,11 @@ class CurrencyResource < JSONAPI::Resource
173
174
  attributes :code, :name
174
175
 
175
176
  has_many :expense_entries
176
- end
177
177
 
178
+ def self.verify_key(key, context = nil)
179
+ key && String(key)
180
+ end
181
+ end
178
182
  ```
179
183
 
180
184
  #### Model Name
@@ -260,11 +264,11 @@ end
260
264
 
261
265
  ##### Finders
262
266
 
263
- Basic finding by filters is supported by resources. This is implemented in the `find`, `find_by_key` and `find_by_keys` finder methods. Currently this is implemented for `ActiveRecord` based resources. The finder methods rely on the `records` method to get an `Arel` relation. It is therefore possible to override `records` to affect the three find related methods.
267
+ Basic finding by filters is supported by resources. This is implemented in the `find` and `find_by_key` finder methods. Currently this is implemented for `ActiveRecord` based resources. The finder methods rely on the `records` method to get an `Arel` relation. It is therefore possible to override `records` to affect the three find related methods.
264
268
 
265
269
  ###### Customizing base records for finder methods
266
270
 
267
- If you need to change the base records on which `find`, `find_by_key` and `find_by_keys` operate, you can override the `records` method on the resource class.
271
+ If you need to change the base records on which `find` and `find_by_key` operate, you can override the `records` method on the resource class.
268
272
 
269
273
  For example to allow a user to only retrieve his own posts you can do the following:
270
274
 
@@ -279,6 +283,41 @@ class PostResource < JSONAPI::Resource
279
283
  end
280
284
  ```
281
285
 
286
+ When you create a relationship, a method is created to fetch record(s) for that relationship. This method calls `records_for(association_name)` by default.
287
+
288
+ ```ruby
289
+ class PostResource < JSONAPI::Resource
290
+ has_one :author
291
+ has_many :comments
292
+
293
+ # def record_for_author(options = {})
294
+ # records_for("author", options)
295
+ # end
296
+
297
+ # def records_for_comments(options = {})
298
+ # records_for("comments", options)
299
+ # end
300
+ end
301
+
302
+ ```
303
+
304
+ For example, you may want raise an error if the user is not authorized to view the associated records.
305
+
306
+ ```ruby
307
+ class BaseResource < JSONAPI::Resource
308
+ def records_for(association_name, options={})
309
+ context = options[:context]
310
+ records = model.public_send(association_name)
311
+
312
+ unless context.current_user.can_view?(records)
313
+ raise NotAuthorizedError
314
+ end
315
+
316
+ records
317
+ end
318
+ end
319
+ ```
320
+
282
321
  ###### Applying Filters
283
322
 
284
323
  The `apply_filter` method is called to apply each filter to the `Arel` relation. You may override this method to gain control over how the filters are applied to the `Arel` relation.
@@ -286,7 +325,7 @@ The `apply_filter` method is called to apply each filter to the `Arel` relation.
286
325
  This example shows how you can implement different approaches for different filters.
287
326
 
288
327
  ```ruby
289
- def apply_filter(records, filter, value)
328
+ def self.apply_filter(records, filter, value)
290
329
  case filter
291
330
  when :visibility
292
331
  records.where('users.publicly_visible = ?', value == :public)
@@ -307,7 +346,7 @@ end
307
346
 
308
347
  ###### Override finder methods
309
348
 
310
- Finally if you have more complex requirements for finding you can override the `find`, `find_by_key` and `find_by_keys` methods on the resource class.
349
+ Finally if you have more complex requirements for finding you can override the `find` and `find_by_key` methods on the resource class.
311
350
 
312
351
  Here's an example that defers the `find` operation to a `current_user` set on the `context` option:
313
352
 
@@ -367,15 +406,15 @@ end
367
406
 
368
407
  ##### Paginator Configuration
369
408
 
370
- The default paginator, which will be used for all resources, is set using `JSONAPI.configure`. For example:
409
+ The default paginator, which will be used for all resources, is set using `JSONAPI.configure`. For example, in your `config/initializers/jsonapi_resources.rb`:
371
410
 
372
411
  ```ruby
373
412
  JSONAPI.configure do |config|
374
413
  # built in paginators are :none, :offset, :cursor, :paged
375
- self.default_paginator = :offset
414
+ config.default_paginator = :offset
376
415
 
377
- self.default_page_size = 10
378
- self.maximum_page_size = 20
416
+ config.default_page_size = 10
417
+ config.maximum_page_size = 20
379
418
  end
380
419
  ```
381
420
 
@@ -630,7 +669,7 @@ The `serialize_to_hash` method also takes some optional parameters:
630
669
 
631
670
  An array of resources. Nested resources can be specified with dot notation.
632
671
 
633
- *Purpose*: determines which objects will be side loaded with the source objects in a linked section
672
+ *Purpose*: determines which objects will be side loaded with the source objects in an `included` section
634
673
 
635
674
  *Example*: ```include: ['comments','author','comments.tags','author.posts']```
636
675
 
@@ -828,9 +867,9 @@ This way all DateTime values will be formatted to display in the specified timez
828
867
 
829
868
  #### Key Format
830
869
 
831
- JSONAPI is agnostic on the format of the keys used in the responses. By default JR uses underscored keys which match the attribute names used by Rails models. This can be changed by specifying a different key formatter.
870
+ By default JR uses dasherized keys as per the [JSON API naming recommendations](http://jsonapi.org/recommendations/#naming). This can be changed by specifying a different key formatter.
832
871
 
833
- For example to use camel cased keys with an initial lowercase character (JSON's default) create an initializer and add the following:
872
+ For example, to use camel cased keys with an initial lowercase character (JSON's default) create an initializer and add the following:
834
873
 
835
874
  ```
836
875
  JSONAPI.configure do |config|
@@ -839,7 +878,7 @@ JSONAPI.configure do |config|
839
878
  end
840
879
  ```
841
880
 
842
- This will cause the serializer to use the `CamelizedKeyFormatter`. Besides `UnderscoredKeyFormatter` and `CamelizedKeyFormatter` JR defines the `DasherizedKeyFormatter`. You can also create your own `KeyFormatter`, for example:
881
+ This will cause the serializer to use the `CamelizedKeyFormatter`. You can also create your own `KeyFormatter`, for example:
843
882
 
844
883
  ```ruby
845
884
  class UpperCamelizedKeyFormatter < JSONAPI::KeyFormatter
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
- spec.required_ruby_version = '>= 1.9.3'
20
+ spec.required_ruby_version = '>= 2.0'
21
21
 
22
22
  spec.add_development_dependency 'bundler', '~> 1.5'
23
23
  spec.add_development_dependency 'rake'
@@ -1,4 +1,5 @@
1
1
  require 'jsonapi/resource'
2
+ require 'jsonapi/resource_controller'
2
3
  require 'jsonapi/resources/version'
3
4
  require 'jsonapi/configuration'
4
5
  require 'jsonapi/paginator'
@@ -6,15 +6,7 @@ module JSONAPI
6
6
  @name = name.to_s
7
7
  @options = options
8
8
  @acts_as_set = options.fetch(:acts_as_set, false) == true
9
- @key = options[:key] ? options[:key].to_sym : nil
10
-
11
- if @key.nil?
12
- @foreign_key = options[:foreign_key ] ? options[:foreign_key ].to_sym : nil
13
- else
14
- # :nocov:
15
- warn '[DEPRECATION] `key` is deprecated in associations. Please use `foreign_key` instead.'
16
- # :nocov:
17
- end
9
+ @foreign_key = options[:foreign_key ] ? options[:foreign_key ].to_sym : nil
18
10
  end
19
11
 
20
12
  def primary_key
@@ -18,6 +18,7 @@ module JSONAPI
18
18
  INVALID_PAGE_OBJECT = 117
19
19
  INVALID_PAGE_VALUE = 118
20
20
  INVALID_SORT_FORMAT = 119
21
+ INVALID_FIELD_FORMAT = 120
21
22
  RECORD_NOT_FOUND = 404
22
23
  UNSUPPORTED_MEDIA_TYPE = 415
23
24
  LOCKED = 423
@@ -102,17 +102,21 @@ module JSONAPI
102
102
  end
103
103
  end
104
104
 
105
- class InvalidLinksObject < Error
106
- attr_accessor :value
107
- def initialize(value)
108
- @value = value
105
+ class InvalidFieldFormat < Error
106
+ def errors
107
+ [JSONAPI::Error.new(code: JSONAPI::INVALID_FIELD_FORMAT,
108
+ status: :bad_request,
109
+ title: 'Invalid field format',
110
+ detail: 'Fields must specify a type.')]
109
111
  end
112
+ end
110
113
 
114
+ class InvalidLinksObject < Error
111
115
  def errors
112
116
  [JSONAPI::Error.new(code: JSONAPI::INVALID_LINKS_OBJECT,
113
117
  status: :bad_request,
114
118
  title: 'Invalid Links Object',
115
- detail: "#{value} is not a valid Links Object.")]
119
+ detail: 'Data is not a valid Links Object.')]
116
120
  end
117
121
  end
118
122
 
@@ -9,19 +9,10 @@ module JSONAPI
9
9
  arg
10
10
  end
11
11
 
12
- # :nocov:
13
- if RUBY_VERSION >= '2.0'
14
- def formatter_for(format)
15
- formatter_class_name = "#{format.to_s.camelize}Formatter"
16
- Object.const_get formatter_class_name if formatter_class_name
17
- end
18
- else
19
- def formatter_for(format)
20
- formatter_class_name = "#{format.to_s.camelize}Formatter"
21
- formatter_class_name.safe_constantize if formatter_class_name
22
- end
12
+ def formatter_for(format)
13
+ formatter_class_name = "#{format.to_s.camelize}Formatter"
14
+ formatter_class_name.safe_constantize if formatter_class_name
23
15
  end
24
- # :nocov:
25
16
  end
26
17
  end
27
18
 
@@ -32,7 +23,7 @@ module JSONAPI
32
23
  end
33
24
 
34
25
  def unformat(formatted_key)
35
- super.to_sym
26
+ super
36
27
  end
37
28
  end
38
29
  end
@@ -44,7 +35,7 @@ module JSONAPI
44
35
  end
45
36
 
46
37
  def unformat(formatted_route)
47
- super.to_sym
38
+ super
48
39
  end
49
40
  end
50
41
  end
@@ -77,7 +68,7 @@ class CamelizedKeyFormatter < JSONAPI::KeyFormatter
77
68
  end
78
69
 
79
70
  def unformat(formatted_key)
80
- formatted_key.to_s.underscore.to_sym
71
+ formatted_key.to_s.underscore
81
72
  end
82
73
  end
83
74
  end
@@ -89,7 +80,7 @@ class DasherizedKeyFormatter < JSONAPI::KeyFormatter
89
80
  end
90
81
 
91
82
  def unformat(formatted_key)
92
- formatted_key.to_s.underscore.to_sym
83
+ formatted_key.to_s.underscore
93
84
  end
94
85
  end
95
86
  end
@@ -121,7 +112,7 @@ class CamelizedRouteFormatter < JSONAPI::RouteFormatter
121
112
  end
122
113
 
123
114
  def unformat(formatted_route)
124
- formatted_route.to_s.underscore.to_sym
115
+ formatted_route.to_s.underscore
125
116
  end
126
117
  end
127
118
  end
@@ -133,7 +124,7 @@ class DasherizedRouteFormatter < JSONAPI::RouteFormatter
133
124
  end
134
125
 
135
126
  def unformat(formatted_route)
136
- formatted_route.to_s.underscore.to_sym
127
+ formatted_route.to_s.underscore
137
128
  end
138
129
  end
139
130
  end
@@ -4,25 +4,14 @@ module JSONAPI
4
4
  end
5
5
 
6
6
  def apply(relation)
7
- # :nocov:
8
- relation
9
- # :nocov:
7
+ # relation
10
8
  end
11
9
 
12
10
  class << self
13
- # :nocov:
14
- if RUBY_VERSION >= '2.0'
15
- def paginator_for(paginator)
16
- paginator_class_name = "#{paginator.to_s.camelize}Paginator"
17
- Object.const_get(paginator_class_name) if paginator_class_name
18
- end
19
- else
20
- def paginator_for(paginator)
21
- paginator_class_name = "#{paginator.to_s.camelize}Paginator"
22
- paginator_class_name.safe_constantize if paginator_class_name
23
- end
11
+ def paginator_for(paginator)
12
+ paginator_class_name = "#{paginator.to_s.camelize}Paginator"
13
+ paginator_class_name.safe_constantize if paginator_class_name
24
14
  end
25
- # :nocov:
26
15
  end
27
16
  end
28
17
  end
@@ -1,11 +1,8 @@
1
- require 'jsonapi/resource_for'
2
1
  require 'jsonapi/operation'
3
2
  require 'jsonapi/paginator'
4
3
 
5
4
  module JSONAPI
6
5
  class Request
7
- include ResourceFor
8
-
9
6
  attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :operations,
10
7
  :resource_klass, :context, :paginator, :source_klass, :source_id
11
8
 
@@ -25,7 +22,7 @@ module JSONAPI
25
22
  end
26
23
 
27
24
  def setup(params)
28
- @resource_klass ||= self.class.resource_for(params[:controller]) if params[:controller]
25
+ @resource_klass ||= Resource.resource_for(params[:controller]) if params[:controller]
29
26
 
30
27
  unless params.nil?
31
28
  case params[:action]
@@ -36,8 +33,8 @@ module JSONAPI
36
33
  parse_sort_criteria(params[:sort])
37
34
  parse_pagination(params[:page])
38
35
  when 'get_related_resource', 'get_related_resources'
39
- @source_klass = self.class.resource_for(params.require(:source))
40
- @source_id = params.require(@source_klass._as_parent_key)
36
+ @source_klass = Resource.resource_for(params.require(:source))
37
+ @source_id = @source_klass.verify_key(params.require(@source_klass._as_parent_key), @context)
41
38
  parse_fields(params[:fields])
42
39
  parse_include(params[:include])
43
40
  parse_filters(params[:filter])
@@ -55,7 +52,7 @@ module JSONAPI
55
52
  params.require(:association),
56
53
  params.require(@resource_klass._as_parent_key))
57
54
  when 'update_association'
58
- parse_update_association_operation(params.require(:data),
55
+ parse_update_association_operation(params.fetch(:data),
59
56
  params.require(:association),
60
57
  params.require(@resource_klass._as_parent_key))
61
58
  when 'update'
@@ -85,15 +82,13 @@ module JSONAPI
85
82
  extracted_fields = {}
86
83
 
87
84
  # Extract the fields for each type from the fields parameters
88
- if fields.is_a?(String)
89
- resource_fields = fields.split(',') unless fields.empty?
90
- type = @resource_klass._type
91
- extracted_fields[type] = resource_fields
92
- elsif fields.is_a?(ActionController::Parameters)
85
+ if fields.is_a?(ActionController::Parameters)
93
86
  fields.each do |field, value|
94
87
  resource_fields = value.split(',') unless value.nil? || value.empty?
95
88
  extracted_fields[field] = resource_fields
96
89
  end
90
+ else
91
+ raise JSONAPI::Exceptions::InvalidFieldFormat.new
97
92
  end
98
93
 
99
94
  # Validate the fields
@@ -101,10 +96,16 @@ module JSONAPI
101
96
  underscored_type = unformat_key(type)
102
97
  extracted_fields[type] = []
103
98
  begin
104
- type_resource = self.class.resource_for(@resource_klass.module_path + underscored_type.to_s)
99
+ if type != format_key(type)
100
+ raise JSONAPI::Exceptions::InvalidResource.new(type)
101
+ end
102
+ type_resource = Resource.resource_for(@resource_klass.module_path + underscored_type.to_s)
105
103
  rescue NameError
106
104
  @errors.concat(JSONAPI::Exceptions::InvalidResource.new(type).errors)
105
+ rescue JSONAPI::Exceptions::InvalidResource => e
106
+ @errors.concat(e.errors)
107
107
  end
108
+
108
109
  if type_resource.nil? || !(@resource_klass._type == underscored_type ||
109
110
  @resource_klass._has_association?(underscored_type))
110
111
  @errors.concat(JSONAPI::Exceptions::InvalidResource.new(type).errors)
@@ -131,7 +132,7 @@ module JSONAPI
131
132
  association_name = unformat_key(include_parts.first)
132
133
 
133
134
  association = resource_klass._association(association_name)
134
- if association
135
+ if association && format_key(association_name) == include_parts.first
135
136
  unless include_parts.last.empty?
136
137
  check_include(Resource.resource_for(@resource_klass.module_path + association.class_name.to_s), include_parts.last.partition('.'))
137
138
  end
@@ -158,7 +159,7 @@ module JSONAPI
158
159
  return unless filters
159
160
  @filters = {}
160
161
  filters.each do |key, value|
161
- filter = unformat_key(key).to_sym
162
+ filter = unformat_key(key)
162
163
  if @resource_klass._allowed_filter?(filter)
163
164
  @filters[filter] = value
164
165
  else
@@ -214,7 +215,7 @@ module JSONAPI
214
215
 
215
216
  def verify_and_remove_type(params)
216
217
  #remove type and verify it matches the resource
217
- if params[:type] == @resource_klass._type.to_s
218
+ if unformat_key(params[:type]) == @resource_klass._type
218
219
  params.delete(:type)
219
220
  else
220
221
  if params[:type].nil?
@@ -235,7 +236,7 @@ module JSONAPI
235
236
  end
236
237
 
237
238
  if !raw.is_a?(Hash) || raw.length != 2 || !(raw.has_key?('type') && raw.has_key?('id'))
238
- raise JSONAPI::Exceptions::InvalidLinksObject.new(raw)
239
+ raise JSONAPI::Exceptions::InvalidLinksObject.new
239
240
  end
240
241
 
241
242
  {
@@ -246,23 +247,18 @@ module JSONAPI
246
247
 
247
248
  def parse_has_many_links_object(raw)
248
249
  if raw.nil?
249
- raise JSONAPI::Exceptions::InvalidLinksObject.new(raw)
250
+ raise JSONAPI::Exceptions::InvalidLinksObject.new
250
251
  end
251
252
 
252
253
  links_object = {}
253
- if raw.is_a?(Hash)
254
- if raw.length != 2 || !(raw.has_key?('type') && raw.has_key?('ids')) || !(raw['ids'].is_a?(Array))
255
- raise JSONAPI::Exceptions::InvalidLinksObject.new(raw)
256
- end
257
- links_object[raw['type']] = raw['ids']
258
- elsif raw.is_a?(Array)
254
+ if raw.is_a?(Array)
259
255
  raw.each do |link|
260
256
  link_object = parse_has_one_links_object(link)
261
257
  links_object[link_object[:type]] ||= []
262
258
  links_object[link_object[:type]].push(link_object[:id])
263
259
  end
264
260
  else
265
- raise JSONAPI::Exceptions::InvalidLinksObject.new(raw)
261
+ raise JSONAPI::Exceptions::InvalidLinksObject.new
266
262
  end
267
263
  links_object
268
264
  end
@@ -291,7 +287,7 @@ module JSONAPI
291
287
  end
292
288
 
293
289
  unless links_object[:id].nil?
294
- association_resource = @resource_klass.resource_for(@resource_klass.module_path + links_object[:type])
290
+ association_resource = Resource.resource_for(@resource_klass.module_path + links_object[:type])
295
291
  checked_has_one_associations[param] = association_resource.verify_key(links_object[:id], @context)
296
292
  else
297
293
  checked_has_one_associations[param] = nil
@@ -311,14 +307,10 @@ module JSONAPI
311
307
  end
312
308
 
313
309
  links_object.each_pair do |type, keys|
314
- association_resource = @resource_klass.resource_for(@resource_klass.module_path + type)
310
+ association_resource = Resource.resource_for(@resource_klass.module_path + type)
315
311
  checked_has_many_associations[param] = association_resource.verify_keys(keys, @context)
316
312
  end
317
313
  end
318
- else
319
- # :nocov:
320
- raise JSONAPI::Exceptions::InvalidLinksObject.new(key)
321
- # :nocov:
322
314
  end
323
315
  end
324
316
  else
@@ -358,23 +350,14 @@ module JSONAPI
358
350
  association = resource_klass._association(association_type)
359
351
 
360
352
  if association.is_a?(JSONAPI::Association::HasMany)
361
- ids = data.require(:ids)
362
- type = data.require(:type)
363
-
364
- object_params = {links: {association.name => {'type' => type, 'ids' => ids}}}
353
+ object_params = {links: {association.name => data}}
365
354
  verified_param_set = parse_params(object_params, @resource_klass.updateable_fields(@context))
366
355
 
367
356
  @operations.push JSONAPI::CreateHasManyAssociationOperation.new(resource_klass,
368
357
  parent_key,
369
358
  association_type,
370
359
  verified_param_set[:has_many].values[0])
371
- else
372
- # :nocov:
373
- @errors.concat(JSONAPI::Exceptions::InvalidLinksObject.new(:data).errors)
374
- # :nocov:
375
360
  end
376
- rescue ActionController::ParameterMissing => e
377
- @errors.concat(JSONAPI::Exceptions::ParameterMissing.new(e.param).errors)
378
361
  end
379
362
 
380
363
  def parse_update_association_operation(data, association_type, parent_key)
@@ -484,7 +467,8 @@ module JSONAPI
484
467
  end
485
468
 
486
469
  def unformat_key(key)
487
- @key_formatter.unformat(key)
470
+ unformatted_key = @key_formatter.unformat(key)
471
+ unformatted_key.nil? ? nil : unformatted_key.to_sym
488
472
  end
489
473
  end
490
474
  end