jsonapi-resources 0.9.3 → 0.10.5

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 (44) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +1 -1
  3. data/README.md +34 -11
  4. data/lib/bug_report_templates/rails_5_latest.rb +125 -0
  5. data/lib/bug_report_templates/rails_5_master.rb +140 -0
  6. data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +27 -0
  7. data/lib/jsonapi/active_relation/join_manager.rb +297 -0
  8. data/lib/jsonapi/active_relation_resource.rb +879 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +122 -105
  10. data/lib/jsonapi/basic_resource.rb +1162 -0
  11. data/lib/jsonapi/cached_response_fragment.rb +127 -0
  12. data/lib/jsonapi/compiled_json.rb +11 -1
  13. data/lib/jsonapi/configuration.rb +63 -8
  14. data/lib/jsonapi/error.rb +27 -0
  15. data/lib/jsonapi/error_codes.rb +2 -0
  16. data/lib/jsonapi/exceptions.rb +63 -40
  17. data/lib/jsonapi/formatter.rb +3 -3
  18. data/lib/jsonapi/include_directives.rb +18 -65
  19. data/lib/jsonapi/link_builder.rb +74 -80
  20. data/lib/jsonapi/operation.rb +16 -5
  21. data/lib/jsonapi/operation_result.rb +74 -16
  22. data/lib/jsonapi/path.rb +43 -0
  23. data/lib/jsonapi/path_segment.rb +76 -0
  24. data/lib/jsonapi/processor.rb +237 -110
  25. data/lib/jsonapi/relationship.rb +144 -15
  26. data/lib/jsonapi/request_parser.rb +412 -357
  27. data/lib/jsonapi/resource.rb +3 -1263
  28. data/lib/jsonapi/resource_controller_metal.rb +5 -2
  29. data/lib/jsonapi/resource_fragment.rb +47 -0
  30. data/lib/jsonapi/resource_id_tree.rb +112 -0
  31. data/lib/jsonapi/resource_identity.rb +42 -0
  32. data/lib/jsonapi/resource_serializer.rb +143 -285
  33. data/lib/jsonapi/resource_set.rb +176 -0
  34. data/lib/jsonapi/resources/railtie.rb +9 -0
  35. data/lib/jsonapi/resources/version.rb +1 -1
  36. data/lib/jsonapi/response_document.rb +105 -83
  37. data/lib/jsonapi/routing_ext.rb +48 -26
  38. data/lib/jsonapi-resources.rb +20 -4
  39. data/lib/tasks/check_upgrade.rake +52 -0
  40. metadata +47 -17
  41. data/lib/jsonapi/cached_resource_fragment.rb +0 -127
  42. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  43. data/lib/jsonapi/operation_results.rb +0 -35
  44. data/lib/jsonapi/relationship_builder.rb +0 -167
@@ -1,121 +1,261 @@
1
- require 'jsonapi/operation'
2
- require 'jsonapi/paginator'
3
-
4
1
  module JSONAPI
5
2
  class RequestParser
6
- attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :operations,
7
- :resource_klass, :context, :paginator, :source_klass, :source_id,
3
+ attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :controller_module_path,
4
+ :context, :paginator, :source_klass, :source_id,
8
5
  :include_directives, :params, :warnings, :server_error_callbacks
9
6
 
10
7
  def initialize(params = nil, options = {})
11
8
  @params = params
9
+ if params
10
+ controller_path = params.fetch(:controller, '')
11
+ @controller_module_path = controller_path.include?('/') ? controller_path.rpartition('/').first + '/' : ''
12
+ else
13
+ @controller_module_path = ''
14
+ end
15
+
12
16
  @context = options[:context]
13
17
  @key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
14
18
  @errors = []
15
19
  @warnings = []
16
- @operations = []
17
- @fields = {}
18
- @filters = {}
19
- @sort_criteria = nil
20
- @source_klass = nil
21
- @source_id = nil
22
- @include_directives = nil
23
- @paginator = nil
24
- @id = nil
25
20
  @server_error_callbacks = options.fetch(:server_error_callbacks, [])
21
+ end
26
22
 
27
- setup_action(@params)
23
+ def error_object_overrides
24
+ {}
28
25
  end
29
26
 
30
- def setup_action(params)
27
+ def each(_response_document)
28
+ operation = setup_base_op(params)
29
+ if @errors.any?
30
+ fail JSONAPI::Exceptions::Errors.new(@errors)
31
+ else
32
+ yield operation
33
+ end
34
+ rescue ActionController::ParameterMissing => e
35
+ fail JSONAPI::Exceptions::ParameterMissing.new(e.param, error_object_overrides)
36
+ end
37
+
38
+ def transactional?
39
+ case params[:action]
40
+ when 'index', 'show_related_resource', 'index_related_resources', 'show', 'show_relationship'
41
+ return false
42
+ else
43
+ return true
44
+ end
45
+ end
46
+
47
+ def setup_base_op(params)
31
48
  return if params.nil?
32
49
 
33
- @resource_klass ||= Resource.resource_for(params[:controller]) if params[:controller]
50
+ resource_klass = Resource.resource_klass_for(params[:controller]) if params[:controller]
34
51
 
35
52
  setup_action_method_name = "setup_#{params[:action]}_action"
36
53
  if respond_to?(setup_action_method_name)
37
54
  raise params[:_parser_exception] if params[:_parser_exception]
38
- send(setup_action_method_name, params)
55
+ send(setup_action_method_name, params, resource_klass)
39
56
  end
40
57
  rescue ActionController::ParameterMissing => e
41
- @errors.concat(JSONAPI::Exceptions::ParameterMissing.new(e.param).errors)
58
+ @errors.concat(JSONAPI::Exceptions::ParameterMissing.new(e.param, error_object_overrides).errors)
59
+ rescue JSONAPI::Exceptions::Error => e
60
+ e.error_object_overrides.merge! error_object_overrides
61
+ @errors.concat(e.errors)
42
62
  end
43
63
 
44
- def setup_index_action(params)
45
- parse_fields(params[:fields])
46
- parse_include_directives(params[:include])
47
- set_default_filters
48
- parse_filters(params[:filter])
49
- parse_sort_criteria(params[:sort])
50
- parse_pagination(params[:page])
51
- add_find_operation
64
+ def setup_index_action(params, resource_klass)
65
+ fields = parse_fields(resource_klass, params[:fields])
66
+ include_directives = parse_include_directives(resource_klass, params[:include])
67
+ filters = parse_filters(resource_klass, params[:filter])
68
+ sort_criteria = parse_sort_criteria(resource_klass, params[:sort])
69
+ paginator = parse_pagination(resource_klass, params[:page])
70
+
71
+ JSONAPI::Operation.new(
72
+ :find,
73
+ resource_klass,
74
+ context: context,
75
+ filters: filters,
76
+ include_directives: include_directives,
77
+ sort_criteria: sort_criteria,
78
+ paginator: paginator,
79
+ fields: fields
80
+ )
52
81
  end
53
82
 
54
- def setup_get_related_resource_action(params)
55
- initialize_source(params)
56
- parse_fields(params[:fields])
57
- parse_include_directives(params[:include])
58
- set_default_filters
59
- parse_filters(params[:filter])
60
- parse_sort_criteria(params[:sort])
61
- parse_pagination(params[:page])
62
- add_show_related_resource_operation(params[:relationship])
83
+ def setup_show_related_resource_action(params, resource_klass)
84
+ resolve_singleton_id(params, resource_klass)
85
+ source_klass = Resource.resource_klass_for(params.require(:source))
86
+ source_id = source_klass.verify_key(params.require(source_klass._as_parent_key), @context)
87
+
88
+ fields = parse_fields(resource_klass, params[:fields])
89
+ include_directives = parse_include_directives(resource_klass, params[:include])
90
+
91
+ relationship_type = params[:relationship].to_sym
92
+
93
+ JSONAPI::Operation.new(
94
+ :show_related_resource,
95
+ resource_klass,
96
+ context: @context,
97
+ relationship_type: relationship_type,
98
+ source_klass: source_klass,
99
+ source_id: source_id,
100
+ fields: fields,
101
+ include_directives: include_directives
102
+ )
63
103
  end
64
104
 
65
- def setup_get_related_resources_action(params)
66
- initialize_source(params)
67
- parse_fields(params[:fields])
68
- parse_include_directives(params[:include])
69
- set_default_filters
70
- parse_filters(params[:filter])
71
- parse_sort_criteria(params[:sort])
72
- parse_pagination(params[:page])
73
- add_show_related_resources_operation(params[:relationship])
105
+ def setup_index_related_resources_action(params, resource_klass)
106
+ resolve_singleton_id(params, resource_klass)
107
+ source_klass = Resource.resource_klass_for(params.require(:source))
108
+ source_id = source_klass.verify_key(params.require(source_klass._as_parent_key), @context)
109
+
110
+ fields = parse_fields(resource_klass, params[:fields])
111
+ include_directives = parse_include_directives(resource_klass, params[:include])
112
+ filters = parse_filters(resource_klass, params[:filter])
113
+ sort_criteria = parse_sort_criteria(resource_klass, params[:sort])
114
+ paginator = parse_pagination(resource_klass, params[:page])
115
+ relationship_type = params[:relationship]
116
+
117
+ JSONAPI::Operation.new(
118
+ :show_related_resources,
119
+ resource_klass,
120
+ context: @context,
121
+ relationship_type: relationship_type,
122
+ source_klass: source_klass,
123
+ source_id: source_id,
124
+ filters: filters,
125
+ sort_criteria: sort_criteria,
126
+ paginator: paginator,
127
+ fields: fields,
128
+ include_directives: include_directives
129
+ )
74
130
  end
75
131
 
76
- def setup_show_action(params)
77
- parse_fields(params[:fields])
78
- parse_include_directives(params[:include])
79
- @id = params[:id]
80
- add_show_operation
132
+ def setup_show_action(params, resource_klass)
133
+ resolve_singleton_id(params, resource_klass)
134
+ fields = parse_fields(resource_klass, params[:fields])
135
+ include_directives = parse_include_directives(resource_klass, params[:include])
136
+ id = params[:id]
137
+
138
+ JSONAPI::Operation.new(
139
+ :show,
140
+ resource_klass,
141
+ context: @context,
142
+ id: id,
143
+ include_directives: include_directives,
144
+ fields: fields,
145
+ allowed_resources: params[:allowed_resources]
146
+ )
81
147
  end
82
148
 
83
- def setup_show_relationship_action(params)
84
- add_show_relationship_operation(params[:relationship], params.require(@resource_klass._as_parent_key))
149
+ def setup_show_relationship_action(params, resource_klass)
150
+ resolve_singleton_id(params, resource_klass)
151
+ relationship_type = params[:relationship]
152
+ parent_key = params.require(resource_klass._as_parent_key)
153
+ include_directives = parse_include_directives(resource_klass, params[:include])
154
+ filters = parse_filters(resource_klass, params[:filter])
155
+ sort_criteria = parse_sort_criteria(resource_klass, params[:sort])
156
+ paginator = parse_pagination(resource_klass, params[:page])
157
+
158
+ JSONAPI::Operation.new(
159
+ :show_relationship,
160
+ resource_klass,
161
+ context: @context,
162
+ relationship_type: relationship_type,
163
+ parent_key: resource_klass.verify_key(parent_key),
164
+ filters: filters,
165
+ sort_criteria: sort_criteria,
166
+ paginator: paginator,
167
+ fields: fields,
168
+ include_directives: include_directives
169
+ )
85
170
  end
86
171
 
87
- def setup_create_action(params)
88
- parse_fields(params[:fields])
89
- parse_include_directives(params[:include])
90
- parse_add_operation(params.require(:data))
172
+ def setup_create_action(params, resource_klass)
173
+ fields = parse_fields(resource_klass, params[:fields])
174
+ include_directives = parse_include_directives(resource_klass, params[:include])
175
+
176
+ data = params.require(:data)
177
+
178
+ unless data.respond_to?(:each_pair)
179
+ fail JSONAPI::Exceptions::InvalidDataFormat.new(error_object_overrides)
180
+ end
181
+
182
+ verify_type(data[:type], resource_klass)
183
+
184
+ data = parse_params(resource_klass, data, resource_klass.creatable_fields(@context))
185
+
186
+ JSONAPI::Operation.new(
187
+ :create_resource,
188
+ resource_klass,
189
+ context: @context,
190
+ data: data,
191
+ fields: fields,
192
+ include_directives: include_directives,
193
+ warnings: @warnings
194
+ )
91
195
  end
92
196
 
93
- def setup_create_relationship_action(params)
94
- parse_modify_relationship_action(params, :add)
197
+ def setup_create_relationship_action(params, resource_klass)
198
+ resolve_singleton_id(params, resource_klass)
199
+ parse_modify_relationship_action(:add, params, resource_klass)
95
200
  end
96
201
 
97
- def setup_update_relationship_action(params)
98
- parse_modify_relationship_action(params, :update)
202
+ def setup_update_relationship_action(params, resource_klass)
203
+ parse_modify_relationship_action(:update, params, resource_klass)
99
204
  end
100
205
 
101
- def setup_update_action(params)
102
- parse_fields(params[:fields])
103
- parse_include_directives(params[:include])
104
- parse_replace_operation(params.require(:data), params[:id])
206
+ def setup_update_action(params, resource_klass)
207
+ resolve_singleton_id(params, resource_klass)
208
+ fields = parse_fields(resource_klass, params[:fields])
209
+ include_directives = parse_include_directives(resource_klass, params[:include])
210
+
211
+ data = params.require(:data)
212
+ key = params[:id]
213
+
214
+ fail JSONAPI::Exceptions::InvalidDataFormat.new(error_object_overrides) unless data.respond_to?(:each_pair)
215
+
216
+ fail JSONAPI::Exceptions::MissingKey.new(error_object_overrides) if data[:id].nil?
217
+
218
+ resource_id = data.require(:id)
219
+ # Singleton resources may not have the ID set in the URL
220
+ if key
221
+ fail JSONAPI::Exceptions::KeyNotIncludedInURL.new(resource_id) if key.to_s != resource_id.to_s
222
+ end
223
+
224
+ data.delete(:id)
225
+
226
+ verify_type(data[:type], resource_klass)
227
+
228
+ JSONAPI::Operation.new(
229
+ :replace_fields,
230
+ resource_klass,
231
+ context: @context,
232
+ resource_id: resource_id,
233
+ data: parse_params(resource_klass, data, resource_klass.updatable_fields(@context)),
234
+ fields: fields,
235
+ include_directives: include_directives,
236
+ warnings: @warnings
237
+ )
105
238
  end
106
239
 
107
- def setup_destroy_action(params)
108
- parse_remove_operation(params)
240
+ def setup_destroy_action(params, resource_klass)
241
+ resolve_singleton_id(params, resource_klass)
242
+ JSONAPI::Operation.new(
243
+ :remove_resource,
244
+ resource_klass,
245
+ context: @context,
246
+ resource_id: resource_klass.verify_key(params.require(:id), @context))
109
247
  end
110
248
 
111
- def setup_destroy_relationship_action(params)
112
- parse_modify_relationship_action(params, :remove)
249
+ def setup_destroy_relationship_action(params, resource_klass)
250
+ resolve_singleton_id(params, resource_klass)
251
+ parse_modify_relationship_action(:remove, params, resource_klass)
113
252
  end
114
253
 
115
- def parse_modify_relationship_action(params, modification_type)
254
+ def parse_modify_relationship_action(modification_type, params, resource_klass)
116
255
  relationship_type = params.require(:relationship)
117
- parent_key = params.require(@resource_klass._as_parent_key)
118
- relationship = @resource_klass._relationship(relationship_type)
256
+
257
+ parent_key = params.require(resource_klass._as_parent_key)
258
+ relationship = resource_klass._relationship(relationship_type)
119
259
 
120
260
  # Removals of to-one relationships are done implicitly and require no specification of data
121
261
  data_required = !(modification_type == :remove && relationship.is_a?(JSONAPI::Relationship::ToOne))
@@ -123,75 +263,74 @@ module JSONAPI
123
263
  if data_required
124
264
  data = params.fetch(:data)
125
265
  object_params = { relationships: { format_key(relationship.name) => { data: data } } }
126
- verified_params = parse_params(object_params, @resource_klass.updatable_fields(@context))
127
266
 
128
- parse_arguments = [verified_params, relationship, parent_key]
267
+ verified_params = parse_params(resource_klass, object_params, resource_klass.updatable_fields(@context))
268
+
269
+ parse_arguments = [resource_klass, verified_params, relationship, parent_key]
129
270
  else
130
- parse_arguments = [params, relationship, parent_key]
271
+ parse_arguments = [resource_klass, params, relationship, parent_key]
131
272
  end
132
273
 
133
274
  send(:"parse_#{modification_type}_relationship_operation", *parse_arguments)
134
275
  end
135
276
 
136
- def initialize_source(params)
137
- @source_klass = Resource.resource_for(params.require(:source))
138
- @source_id = @source_klass.verify_key(params.require(@source_klass._as_parent_key), @context)
277
+ def parse_pagination(resource_klass, page)
278
+ paginator_name = resource_klass._paginator
279
+ JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none
139
280
  end
140
281
 
141
- def parse_pagination(page)
142
- paginator_name = @resource_klass._paginator
143
- @paginator = JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none
144
- rescue JSONAPI::Exceptions::Error => e
145
- @errors.concat(e.errors)
146
- end
147
-
148
- def parse_fields(fields)
149
- return if fields.nil?
150
-
282
+ def parse_fields(resource_klass, fields)
151
283
  extracted_fields = {}
152
284
 
285
+ return extracted_fields if fields.nil?
286
+
153
287
  # Extract the fields for each type from the fields parameters
154
288
  if fields.is_a?(ActionController::Parameters)
155
289
  fields.each do |field, value|
156
- resource_fields = value.split(',') unless value.nil? || value.empty?
290
+ if value.is_a?(Array)
291
+ resource_fields = value
292
+ else
293
+ resource_fields = value.split(',') unless value.nil? || value.empty?
294
+ end
157
295
  extracted_fields[field] = resource_fields
158
296
  end
159
297
  else
160
- fail JSONAPI::Exceptions::InvalidFieldFormat.new
298
+ fail JSONAPI::Exceptions::InvalidFieldFormat.new(error_object_overrides)
161
299
  end
162
300
 
163
301
  # Validate the fields
302
+ validated_fields = {}
164
303
  extracted_fields.each do |type, values|
165
304
  underscored_type = unformat_key(type)
166
- extracted_fields[type] = []
305
+ validated_fields[type] = []
167
306
  begin
168
307
  if type != format_key(type)
169
- fail JSONAPI::Exceptions::InvalidResource.new(type)
308
+ fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
170
309
  end
171
- type_resource = Resource.resource_for(@resource_klass.module_path + underscored_type.to_s)
310
+ type_resource = Resource.resource_klass_for(resource_klass.module_path + underscored_type.to_s)
172
311
  rescue NameError
173
- fail JSONAPI::Exceptions::InvalidResource.new(type)
312
+ fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
174
313
  end
175
314
 
176
315
  if type_resource.nil?
177
- fail JSONAPI::Exceptions::InvalidResource.new(type)
316
+ fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
178
317
  else
179
318
  unless values.nil?
180
319
  valid_fields = type_resource.fields.collect { |key| format_key(key) }
181
320
  values.each do |field|
182
321
  if valid_fields.include?(field)
183
- extracted_fields[type].push unformat_key(field)
322
+ validated_fields[type].push unformat_key(field)
184
323
  else
185
- fail JSONAPI::Exceptions::InvalidField.new(type, field)
324
+ fail JSONAPI::Exceptions::InvalidField.new(type, field, error_object_overrides)
186
325
  end
187
326
  end
188
327
  else
189
- fail JSONAPI::Exceptions::InvalidField.new(type, 'nil')
328
+ fail JSONAPI::Exceptions::InvalidField.new(type, 'nil', error_object_overrides)
190
329
  end
191
330
  end
192
331
  end
193
332
 
194
- @fields = extracted_fields.deep_transform_keys { |key| unformat_key(key) }
333
+ validated_fields.deep_transform_keys { |key| unformat_key(key) }
195
334
  end
196
335
 
197
336
  def check_include(resource_klass, include_parts)
@@ -199,214 +338,144 @@ module JSONAPI
199
338
 
200
339
  relationship = resource_klass._relationship(relationship_name)
201
340
  if relationship && format_key(relationship_name) == include_parts.first
341
+ unless relationship.allow_include?(context)
342
+ fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), include_parts.first)
343
+ end
344
+
202
345
  unless include_parts.last.empty?
203
- check_include(Resource.resource_for(resource_klass.module_path + relationship.class_name.to_s.underscore), include_parts.last.partition('.'))
346
+ check_include(Resource.resource_klass_for(resource_klass.module_path + relationship.class_name.to_s.underscore),
347
+ include_parts.last.partition('.'))
204
348
  end
205
349
  else
206
350
  fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), include_parts.first)
207
351
  end
352
+ true
208
353
  end
209
354
 
210
- def parse_include_directives(raw_include)
211
- return unless raw_include
212
-
213
- unless JSONAPI.configuration.allow_include
214
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(:include)
215
- end
355
+ def parse_include_directives(resource_klass, raw_include)
356
+ raw_include ||= ''
216
357
 
217
358
  included_resources = []
218
359
  begin
219
- included_resources += CSV.parse_line(raw_include)
360
+ included_resources += raw_include.is_a?(Array) ? raw_include : CSV.parse_line(raw_include) || []
220
361
  rescue CSV::MalformedCSVError
221
- fail JSONAPI::Exceptions::InvalidInclude.new(format_key(@resource_klass._type), raw_include)
362
+ fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), raw_include)
222
363
  end
223
364
 
224
- return if included_resources.empty?
225
-
226
365
  begin
227
366
  result = included_resources.compact.map do |included_resource|
228
- check_include(@resource_klass, included_resource.partition('.'))
367
+ check_include(resource_klass, included_resource.partition('.'))
229
368
  unformat_key(included_resource).to_s
230
369
  end
231
370
 
232
- @include_directives = JSONAPI::IncludeDirectives.new(@resource_klass, result)
371
+ return JSONAPI::IncludeDirectives.new(resource_klass, result)
233
372
  rescue JSONAPI::Exceptions::InvalidInclude => e
234
373
  @errors.concat(e.errors)
235
- @include_directives = {}
374
+ return {}
236
375
  end
237
376
  end
238
377
 
239
- def parse_filters(filters)
240
- return unless filters
378
+ def parse_filters(resource_klass, filters)
379
+ parsed_filters = {}
241
380
 
242
- unless JSONAPI.configuration.allow_filter
243
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(:filter)
381
+ # apply default filters
382
+ resource_klass._allowed_filters.each do |filter, opts|
383
+ next if opts[:default].nil? || !parsed_filters[filter].nil?
384
+ parsed_filters[filter] = opts[:default]
244
385
  end
245
386
 
387
+ return parsed_filters unless filters
388
+
246
389
  unless filters.class.method_defined?(:each)
247
390
  @errors.concat(JSONAPI::Exceptions::InvalidFiltersSyntax.new(filters).errors)
248
- return
391
+ return {}
392
+ end
393
+
394
+ unless JSONAPI.configuration.allow_filter
395
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(:filter)
249
396
  end
250
397
 
251
398
  filters.each do |key, value|
252
399
  filter = unformat_key(key)
253
- if @resource_klass._allowed_filter?(filter)
254
- @filters[filter] = value
400
+ if resource_klass._allowed_filter?(filter)
401
+ parsed_filters[filter] = value
255
402
  else
256
- fail JSONAPI::Exceptions::FilterNotAllowed.new(filter)
403
+ fail JSONAPI::Exceptions::FilterNotAllowed.new(key)
257
404
  end
258
405
  end
259
- end
260
406
 
261
- def set_default_filters
262
- @resource_klass._allowed_filters.each do |filter, opts|
263
- next if opts[:default].nil? || !@filters[filter].nil?
264
- @filters[filter] = opts[:default]
265
- end
407
+ parsed_filters
266
408
  end
267
409
 
268
- def parse_sort_criteria(sort_criteria)
410
+ def parse_sort_criteria(resource_klass, sort_criteria)
269
411
  return unless sort_criteria.present?
270
412
 
271
413
  unless JSONAPI.configuration.allow_sort
272
414
  fail JSONAPI::Exceptions::ParameterNotAllowed.new(:sort)
273
415
  end
274
416
 
275
- sorts = []
276
- begin
277
- raw = URI.unescape(sort_criteria)
278
- sorts += CSV.parse_line(raw)
279
- rescue CSV::MalformedCSVError
280
- fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(@resource_klass._type), raw)
417
+ if sort_criteria.is_a?(Array)
418
+ sorts = sort_criteria
419
+ elsif sort_criteria.is_a?(String)
420
+ begin
421
+ raw = URI.decode_www_form_component(sort_criteria)
422
+ sorts = CSV.parse_line(raw)
423
+ rescue CSV::MalformedCSVError
424
+ fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(resource_klass._type), raw)
425
+ end
281
426
  end
282
427
 
283
428
  @sort_criteria = sorts.collect do |sort|
284
429
  if sort.start_with?('-')
285
- sort_criteria = { field: unformat_key(sort[1..-1]).to_s }
286
- sort_criteria[:direction] = :desc
430
+ criteria = { field: unformat_key(sort[1..-1]).to_s }
431
+ criteria[:direction] = :desc
287
432
  else
288
- sort_criteria = { field: unformat_key(sort).to_s }
289
- sort_criteria[:direction] = :asc
433
+ criteria = { field: unformat_key(sort).to_s }
434
+ criteria[:direction] = :asc
290
435
  end
291
436
 
292
- check_sort_criteria(@resource_klass, sort_criteria)
293
- sort_criteria
437
+ check_sort_criteria(resource_klass, criteria)
438
+ criteria
294
439
  end
295
440
  end
296
441
 
297
442
  def check_sort_criteria(resource_klass, sort_criteria)
298
443
  sort_field = sort_criteria[:field]
299
- sortable_fields = resource_klass.sortable_fields(context)
300
444
 
301
- unless sortable_fields.include?sort_field.to_sym
445
+ unless resource_klass.sortable_field?(sort_field.to_sym, context)
302
446
  fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(resource_klass._type), sort_field)
303
447
  end
304
448
  end
305
449
 
306
- def add_find_operation
307
- @operations.push JSONAPI::Operation.new(:find,
308
- @resource_klass,
309
- context: @context,
310
- filters: @filters,
311
- include_directives: @include_directives,
312
- sort_criteria: @sort_criteria,
313
- paginator: @paginator,
314
- fields: @fields
315
- )
316
- end
317
-
318
- def add_show_operation
319
- @operations.push JSONAPI::Operation.new(:show,
320
- @resource_klass,
321
- context: @context,
322
- id: @id,
323
- include_directives: @include_directives,
324
- fields: @fields
325
- )
326
- end
327
-
328
- def add_show_relationship_operation(relationship_type, parent_key)
329
- @operations.push JSONAPI::Operation.new(:show_relationship,
330
- @resource_klass,
331
- context: @context,
332
- relationship_type: relationship_type,
333
- parent_key: @resource_klass.verify_key(parent_key)
334
- )
335
- end
336
-
337
- def add_show_related_resource_operation(relationship_type)
338
- @operations.push JSONAPI::Operation.new(:show_related_resource,
339
- @resource_klass,
340
- context: @context,
341
- relationship_type: relationship_type,
342
- source_klass: @source_klass,
343
- source_id: @source_id,
344
- fields: @fields,
345
- include_directives: @include_directives
346
- )
347
- end
348
-
349
- def add_show_related_resources_operation(relationship_type)
350
- @operations.push JSONAPI::Operation.new(:show_related_resources,
351
- @resource_klass,
352
- context: @context,
353
- relationship_type: relationship_type,
354
- source_klass: @source_klass,
355
- source_id: @source_id,
356
- filters: @filters,
357
- sort_criteria: @sort_criteria,
358
- paginator: @paginator,
359
- fields: @fields,
360
- include_directives: @include_directives
361
- )
362
- end
363
-
364
- def parse_add_operation(params)
365
- fail JSONAPI::Exceptions::InvalidDataFormat unless params.respond_to?(:each_pair)
366
-
367
- verify_type(params[:type])
368
-
369
- data = parse_params(params, @resource_klass.creatable_fields(@context))
370
- @operations.push JSONAPI::Operation.new(:create_resource,
371
- @resource_klass,
372
- context: @context,
373
- data: data,
374
- fields: @fields,
375
- include_directives: @include_directives
376
- )
377
- rescue JSONAPI::Exceptions::Error => e
378
- @errors.concat(e.errors)
379
- end
380
-
381
- def verify_type(type)
450
+ def verify_type(type, resource_klass)
382
451
  if type.nil?
383
452
  fail JSONAPI::Exceptions::ParameterMissing.new(:type)
384
- elsif unformat_key(type).to_sym != @resource_klass._type
385
- fail JSONAPI::Exceptions::InvalidResource.new(type)
453
+ elsif unformat_key(type).to_sym != resource_klass._type
454
+ fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
386
455
  end
387
456
  end
388
457
 
389
458
  def parse_to_one_links_object(raw)
390
459
  if raw.nil?
391
460
  return {
392
- type: nil,
393
- id: nil
461
+ type: nil,
462
+ id: nil
394
463
  }
395
464
  end
396
465
 
397
466
  if !(raw.is_a?(Hash) || raw.is_a?(ActionController::Parameters)) ||
398
- raw.keys.length != 2 || !(raw.key?('type') && raw.key?('id'))
399
- fail JSONAPI::Exceptions::InvalidLinksObject.new
467
+ raw.keys.length != 2 || !(raw.key?('type') && raw.key?('id'))
468
+ fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides)
400
469
  end
401
470
 
402
471
  {
403
- type: unformat_key(raw['type']).to_s,
404
- id: raw['id']
472
+ type: unformat_key(raw['type']).to_s,
473
+ id: raw['id']
405
474
  }
406
475
  end
407
476
 
408
477
  def parse_to_many_links_object(raw)
409
- fail JSONAPI::Exceptions::InvalidLinksObject.new if raw.nil?
478
+ fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides) if raw.nil?
410
479
 
411
480
  links_object = {}
412
481
  if raw.is_a?(Array)
@@ -416,12 +485,12 @@ module JSONAPI
416
485
  links_object[link_object[:type]].push(link_object[:id])
417
486
  end
418
487
  else
419
- fail JSONAPI::Exceptions::InvalidLinksObject.new
488
+ fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides)
420
489
  end
421
490
  links_object
422
491
  end
423
492
 
424
- def parse_params(params, allowed_fields)
493
+ def parse_params(resource_klass, params, allowed_fields)
425
494
  verify_permitted_params(params, allowed_fields)
426
495
 
427
496
  checked_attributes = {}
@@ -430,37 +499,37 @@ module JSONAPI
430
499
 
431
500
  params.each do |key, value|
432
501
  case key.to_s
433
- when 'relationships'
434
- value.each do |link_key, link_value|
435
- param = unformat_key(link_key)
436
- relationship = @resource_klass._relationship(param)
437
-
438
- if relationship.is_a?(JSONAPI::Relationship::ToOne)
439
- checked_to_one_relationships[param] = parse_to_one_relationship(link_value, relationship)
440
- elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
441
- parse_to_many_relationship(link_value, relationship) do |result_val|
442
- checked_to_many_relationships[param] = result_val
502
+ when 'relationships'
503
+ value.each do |link_key, link_value|
504
+ param = unformat_key(link_key)
505
+ relationship = resource_klass._relationship(param)
506
+
507
+ if relationship.is_a?(JSONAPI::Relationship::ToOne)
508
+ checked_to_one_relationships[param] = parse_to_one_relationship(resource_klass, link_value, relationship)
509
+ elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
510
+ parse_to_many_relationship(resource_klass, link_value, relationship) do |result_val|
511
+ checked_to_many_relationships[param] = result_val
512
+ end
443
513
  end
444
514
  end
445
- end
446
- when 'id'
447
- checked_attributes['id'] = unformat_value(:id, value)
448
- when 'attributes'
449
- value.each do |key, value|
450
- param = unformat_key(key)
451
- checked_attributes[param] = unformat_value(param, value)
452
- end
515
+ when 'id'
516
+ checked_attributes['id'] = unformat_value(resource_klass, :id, value)
517
+ when 'attributes'
518
+ value.each do |key, value|
519
+ param = unformat_key(key)
520
+ checked_attributes[param] = unformat_value(resource_klass, param, value)
521
+ end
453
522
  end
454
523
  end
455
524
 
456
525
  return {
457
- 'attributes' => checked_attributes,
458
- 'to_one' => checked_to_one_relationships,
459
- 'to_many' => checked_to_many_relationships
526
+ 'attributes' => checked_attributes,
527
+ 'to_one' => checked_to_one_relationships,
528
+ 'to_many' => checked_to_many_relationships
460
529
  }.deep_transform_keys { |key| unformat_key(key) }
461
530
  end
462
531
 
463
- def parse_to_one_relationship(link_value, relationship)
532
+ def parse_to_one_relationship(resource_klass, link_value, relationship)
464
533
  if link_value.nil?
465
534
  linkage = nil
466
535
  else
@@ -469,12 +538,12 @@ module JSONAPI
469
538
 
470
539
  links_object = parse_to_one_links_object(linkage)
471
540
  if !relationship.polymorphic? && links_object[:type] && (links_object[:type].to_s != relationship.type.to_s)
472
- fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
541
+ fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type], error_object_overrides)
473
542
  end
474
543
 
475
544
  unless links_object[:id].nil?
476
- resource = self.resource_klass || Resource
477
- relationship_resource = resource.resource_for(unformat_key(relationship.options[:class_name] || links_object[:type]).to_s)
545
+ resource = resource_klass || Resource
546
+ relationship_resource = resource.resource_klass_for(unformat_key(relationship.options[:class_name] || links_object[:type]).to_s)
478
547
  relationship_id = relationship_resource.verify_key(links_object[:id], @context)
479
548
  if relationship.polymorphic?
480
549
  { id: relationship_id, type: unformat_key(links_object[:type].to_s) }
@@ -486,37 +555,54 @@ module JSONAPI
486
555
  end
487
556
  end
488
557
 
489
- def parse_to_many_relationship(link_value, relationship, &add_result)
490
- if link_value.is_a?(Array) && link_value.length == 0
491
- linkage = []
492
- elsif (link_value.is_a?(Hash) || link_value.is_a?(ActionController::Parameters))
558
+ def parse_to_many_relationship(resource_klass, link_value, relationship, &add_result)
559
+ if (link_value.is_a?(Hash) || link_value.is_a?(ActionController::Parameters))
493
560
  linkage = link_value[:data]
494
561
  else
495
- fail JSONAPI::Exceptions::InvalidLinksObject.new
562
+ fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides)
496
563
  end
497
564
 
498
565
  links_object = parse_to_many_links_object(linkage)
499
566
 
500
- # Since we do not yet support polymorphic to_many relationships we will raise an error if the type does not match the
501
- # relationship's type.
502
- # ToDo: Support Polymorphic relationships
503
-
504
567
  if links_object.length == 0
505
568
  add_result.call([])
506
569
  else
507
- if links_object.length > 1 || !links_object.has_key?(unformat_key(relationship.type).to_s)
508
- fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
509
- end
570
+ if relationship.polymorphic?
571
+ polymorphic_results = []
572
+
573
+ links_object.each_pair do |type, keys|
574
+ type_name = unformat_key(type).to_s
575
+
576
+ relationship_resource_klass = resource_klass.resource_klass_for(relationship.class_name)
577
+ relationship_klass = relationship_resource_klass._model_class
578
+
579
+ linkage_object_resource_klass = resource_klass.resource_klass_for(type_name)
580
+ linkage_object_klass = linkage_object_resource_klass._model_class
581
+
582
+ unless linkage_object_klass == relationship_klass || linkage_object_klass.in?(relationship_klass.subclasses)
583
+ fail JSONAPI::Exceptions::TypeMismatch.new(type_name)
584
+ end
585
+
586
+ relationship_ids = relationship_resource_klass.verify_keys(keys, @context)
587
+ polymorphic_results << { type: type, ids: relationship_ids }
588
+ end
589
+
590
+ add_result.call polymorphic_results
591
+ else
592
+ relationship_type = unformat_key(relationship.type).to_s
593
+
594
+ if links_object.length > 1 || !links_object.has_key?(relationship_type)
595
+ fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
596
+ end
510
597
 
511
- links_object.each_pair do |type, keys|
512
- relationship_resource = Resource.resource_for(@resource_klass.module_path + unformat_key(type).to_s)
513
- add_result.call relationship_resource.verify_keys(keys, @context)
598
+ relationship_resource_klass = Resource.resource_klass_for(resource_klass.module_path + relationship_type)
599
+ add_result.call relationship_resource_klass.verify_keys(links_object[relationship_type], @context)
514
600
  end
515
601
  end
516
602
  end
517
603
 
518
- def unformat_value(attribute, value)
519
- value_formatter = JSONAPI::ValueFormatter.value_formatter_for(@resource_klass._attribute_options(attribute)[:format])
604
+ def unformat_value(resource_klass, attribute, value)
605
+ value_formatter = JSONAPI::ValueFormatter.value_formatter_for(resource_klass._attribute_options(attribute)[:format])
520
606
  value_formatter.unformat(value)
521
607
  end
522
608
 
@@ -526,45 +612,45 @@ module JSONAPI
526
612
 
527
613
  params.each do |key, value|
528
614
  case key.to_s
529
- when 'relationships'
530
- value.keys.each do |links_key|
531
- unless formatted_allowed_fields.include?(links_key.to_sym)
532
- if JSONAPI.configuration.raise_if_parameters_not_allowed
533
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(links_key)
534
- else
535
- params_not_allowed.push(links_key)
536
- value.delete links_key
615
+ when 'relationships'
616
+ value.keys.each do |links_key|
617
+ unless formatted_allowed_fields.include?(links_key.to_sym)
618
+ if JSONAPI.configuration.raise_if_parameters_not_allowed
619
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(links_key, error_object_overrides)
620
+ else
621
+ params_not_allowed.push(links_key)
622
+ value.delete links_key
623
+ end
537
624
  end
538
625
  end
539
- end
540
- when 'attributes'
541
- value.each do |attr_key, attr_value|
542
- unless formatted_allowed_fields.include?(attr_key.to_sym)
626
+ when 'attributes'
627
+ value.each do |attr_key, _attr_value|
628
+ unless formatted_allowed_fields.include?(attr_key.to_sym)
629
+ if JSONAPI.configuration.raise_if_parameters_not_allowed
630
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(attr_key, error_object_overrides)
631
+ else
632
+ params_not_allowed.push(attr_key)
633
+ value.delete attr_key
634
+ end
635
+ end
636
+ end
637
+ when 'type'
638
+ when 'id'
639
+ unless formatted_allowed_fields.include?(:id)
543
640
  if JSONAPI.configuration.raise_if_parameters_not_allowed
544
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(attr_key)
641
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(:id, error_object_overrides)
545
642
  else
546
- params_not_allowed.push(attr_key)
547
- value.delete attr_key
643
+ params_not_allowed.push(:id)
644
+ params.delete :id
548
645
  end
549
646
  end
550
- end
551
- when 'type'
552
- when 'id'
553
- unless formatted_allowed_fields.include?(:id)
647
+ else
554
648
  if JSONAPI.configuration.raise_if_parameters_not_allowed
555
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(:id)
649
+ fail JSONAPI::Exceptions::ParameterNotAllowed.new(key, error_object_overrides)
556
650
  else
557
- params_not_allowed.push(:id)
558
- params.delete :id
651
+ params_not_allowed.push(key)
652
+ params.delete key
559
653
  end
560
- end
561
- else
562
- if JSONAPI.configuration.raise_if_parameters_not_allowed
563
- fail JSONAPI::Exceptions::ParameterNotAllowed.new(key)
564
- else
565
- params_not_allowed.push(key)
566
- params.delete key
567
- end
568
654
  end
569
655
  end
570
656
 
@@ -578,23 +664,24 @@ module JSONAPI
578
664
  end
579
665
  end
580
666
 
581
- def parse_add_relationship_operation(verified_params, relationship, parent_key)
667
+ def parse_add_relationship_operation(resource_klass, verified_params, relationship, parent_key)
582
668
  if relationship.is_a?(JSONAPI::Relationship::ToMany)
583
- @operations.push JSONAPI::Operation.new(:create_to_many_relationships,
584
- resource_klass,
585
- context: @context,
586
- resource_id: parent_key,
587
- relationship_type: relationship.name,
588
- data: verified_params[:to_many].values[0]
669
+ return JSONAPI::Operation.new(
670
+ :create_to_many_relationships,
671
+ resource_klass,
672
+ context: @context,
673
+ resource_id: parent_key,
674
+ relationship_type: relationship.name,
675
+ data: verified_params[:to_many].values[0]
589
676
  )
590
677
  end
591
678
  end
592
679
 
593
- def parse_update_relationship_operation(verified_params, relationship, parent_key)
680
+ def parse_update_relationship_operation(resource_klass, verified_params, relationship, parent_key)
594
681
  options = {
595
- context: @context,
596
- resource_id: parent_key,
597
- relationship_type: relationship.name
682
+ context: @context,
683
+ resource_id: parent_key,
684
+ relationship_type: relationship.name
598
685
  }
599
686
 
600
687
  if relationship.is_a?(JSONAPI::Relationship::ToOne)
@@ -615,62 +702,30 @@ module JSONAPI
615
702
  operation_type = :replace_to_many_relationships
616
703
  end
617
704
 
618
- @operations.push JSONAPI::Operation.new(operation_type, resource_klass, options)
705
+ JSONAPI::Operation.new(operation_type, resource_klass, options)
619
706
  end
620
707
 
621
- def parse_single_replace_operation(data, keys, id_key_presence_check_required: true)
622
- fail JSONAPI::Exceptions::InvalidDataFormat unless data.respond_to?(:each_pair)
623
-
624
- fail JSONAPI::Exceptions::MissingKey.new if data[:id].nil?
625
-
626
- key = data[:id].to_s
627
- if id_key_presence_check_required && !keys.include?(key)
628
- fail JSONAPI::Exceptions::KeyNotIncludedInURL.new(key)
629
- end
630
-
631
- data.delete(:id) unless keys.include?(:id)
632
-
633
- verify_type(data[:type])
634
-
635
- @operations.push JSONAPI::Operation.new(:replace_fields,
636
- @resource_klass,
637
- context: @context,
638
- resource_id: key,
639
- data: parse_params(data, @resource_klass.updatable_fields(@context)),
640
- fields: @fields,
641
- include_directives: @include_directives
642
- )
643
- end
644
-
645
- def parse_replace_operation(data, keys)
646
- parse_single_replace_operation(data, [keys], id_key_presence_check_required: keys.present?)
647
- rescue JSONAPI::Exceptions::Error => e
648
- @errors.concat(e.errors)
649
- end
650
-
651
- def parse_remove_operation(params)
652
- @operations.push JSONAPI::Operation.new(:remove_resource,
653
- @resource_klass,
654
- context: @context,
655
- resource_id: @resource_klass.verify_key(params.require(:id), context))
656
- rescue JSONAPI::Exceptions::Error => e
657
- @errors.concat(e.errors)
658
- end
659
-
660
- def parse_remove_relationship_operation(params, relationship, parent_key)
708
+ def parse_remove_relationship_operation(resource_klass, params, relationship, parent_key)
661
709
  operation_base_args = [resource_klass].push(
662
- context: @context,
663
- resource_id: parent_key,
664
- relationship_type: relationship.name
710
+ context: @context,
711
+ resource_id: parent_key,
712
+ relationship_type: relationship.name
665
713
  )
666
714
 
667
715
  if relationship.is_a?(JSONAPI::Relationship::ToMany)
668
716
  operation_args = operation_base_args.dup
669
717
  keys = params[:to_many].values[0]
670
718
  operation_args[1] = operation_args[1].merge(associated_keys: keys)
671
- @operations.push JSONAPI::Operation.new(:remove_to_many_relationships, *operation_args)
719
+ JSONAPI::Operation.new(:remove_to_many_relationships, *operation_args)
672
720
  else
673
- @operations.push JSONAPI::Operation.new(:remove_to_one_relationship, *operation_base_args)
721
+ JSONAPI::Operation.new(:remove_to_one_relationship, *operation_base_args)
722
+ end
723
+ end
724
+
725
+ def resolve_singleton_id(params, resource_klass)
726
+ if resource_klass.singleton? && params[:id].nil?
727
+ key = resource_klass.singleton_key(context)
728
+ params[:id] = key
674
729
  end
675
730
  end
676
731