jsonapi-resources 0.9.12 → 0.10.7

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