jsonapi-resources 0.9.0 → 0.10.6

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 +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 +303 -0
  8. data/lib/jsonapi/active_relation_resource.rb +884 -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 +71 -8
  14. data/lib/jsonapi/error.rb +27 -0
  15. data/lib/jsonapi/error_codes.rb +2 -0
  16. data/lib/jsonapi/exceptions.rb +80 -50
  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 +239 -111
  25. data/lib/jsonapi/relationship.rb +153 -15
  26. data/lib/jsonapi/request_parser.rb +430 -367
  27. data/lib/jsonapi/resource.rb +3 -1253
  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 +50 -20
  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
@@ -0,0 +1,127 @@
1
+ module JSONAPI
2
+ class CachedResponseFragment
3
+
4
+ Lookup = Struct.new(:resource_klass, :serializer_config_key, :context, :context_key, :cache_ids) do
5
+
6
+ def type
7
+ resource_klass._type
8
+ end
9
+
10
+ def keys
11
+ cache_ids.map do |(id, cache_key)|
12
+ [type, id, cache_key, serializer_config_key, context_key]
13
+ end
14
+ end
15
+ end
16
+
17
+ Write = Struct.new(:resource_klass, :resource, :serializer, :serializer_config_key, :context, :context_key, :relationship_data) do
18
+ def to_key_value
19
+
20
+ (id, cache_key) = resource.cache_id
21
+
22
+ json = serializer.object_hash(resource, relationship_data)
23
+
24
+ cr = CachedResponseFragment.new(
25
+ resource_klass,
26
+ id,
27
+ json['type'],
28
+ context,
29
+ resource.fetchable_fields,
30
+ json['relationships'],
31
+ json['links'],
32
+ json['attributes'],
33
+ json['meta']
34
+ )
35
+
36
+ key = [resource_klass._type, id, cache_key, serializer_config_key, context_key]
37
+
38
+ [key, cr]
39
+ end
40
+ end
41
+
42
+ attr_reader :resource_klass, :id, :type, :context, :fetchable_fields, :relationships,
43
+ :links_json, :attributes_json, :meta_json
44
+
45
+ def initialize(resource_klass, id, type, context, fetchable_fields, relationships,
46
+ links_json, attributes_json, meta_json)
47
+ @resource_klass = resource_klass
48
+ @id = id
49
+ @type = type
50
+ @context = context
51
+ @fetchable_fields = Set.new(fetchable_fields)
52
+
53
+ # Relationships left uncompiled because we'll often want to insert included ids on retrieval
54
+ # Remove the data since that should not be cached
55
+ @relationships = relationships&.transform_values {|v| v.delete_if {|k, _v| k == 'data'} }
56
+ @links_json = CompiledJson.of(links_json)
57
+ @attributes_json = CompiledJson.of(attributes_json)
58
+ @meta_json = CompiledJson.of(meta_json)
59
+ end
60
+
61
+ def to_cache_value
62
+ {
63
+ id: id,
64
+ type: type,
65
+ fetchable: fetchable_fields,
66
+ rels: relationships,
67
+ links: links_json.try(:to_s),
68
+ attrs: attributes_json.try(:to_s),
69
+ meta: meta_json.try(:to_s)
70
+ }
71
+ end
72
+
73
+ # @param [Lookup[]] lookups
74
+ # @return [Hash<Class<Resource>, Hash<ID, CachedResourceFragment>>]
75
+ def self.lookup(lookups, context)
76
+ type_to_klass = lookups.map {|l| [l.type, l.resource_klass]}.to_h
77
+
78
+ keys = lookups.map(&:keys).flatten(1)
79
+
80
+ hits = JSONAPI.configuration.resource_cache.read_multi(*keys).reject {|_, v| v.nil?}
81
+
82
+ return keys.inject({}) do |hash, key|
83
+ (type, id, _, _) = key
84
+ resource_klass = type_to_klass[type]
85
+ hash[resource_klass] ||= {}
86
+
87
+ if hits.has_key?(key)
88
+ hash[resource_klass][id] = self.from_cache_value(resource_klass, context, hits[key])
89
+ else
90
+ hash[resource_klass][id] = nil
91
+ end
92
+
93
+ hash
94
+ end
95
+ end
96
+
97
+ # @param [Write[]] lookups
98
+ def self.write(writes)
99
+ key_values = writes.map(&:to_key_value)
100
+
101
+ to_write = key_values.map {|(k, v)| [k, v.to_cache_value]}.to_h
102
+
103
+ if JSONAPI.configuration.resource_cache.respond_to? :write_multi
104
+ JSONAPI.configuration.resource_cache.write_multi(to_write)
105
+ else
106
+ to_write.each do |key, value|
107
+ JSONAPI.configuration.resource_cache.write(key, value)
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ def self.from_cache_value(resource_klass, context, h)
114
+ new(
115
+ resource_klass,
116
+ h.fetch(:id),
117
+ h.fetch(:type),
118
+ context,
119
+ h.fetch(:fetchable),
120
+ h.fetch(:rels, nil),
121
+ h.fetch(:links, nil),
122
+ h.fetch(:attrs, nil),
123
+ h.fetch(:meta, nil)
124
+ )
125
+ end
126
+ end
127
+ end
@@ -5,6 +5,7 @@ module JSONAPI
5
5
  end
6
6
 
7
7
  def self.of(obj)
8
+ # :nocov:
8
9
  case obj
9
10
  when NilClass then nil
10
11
  when CompiledJson then obj
@@ -12,6 +13,7 @@ module JSONAPI
12
13
  when Hash then CompiledJson.compile(obj)
13
14
  else raise "Can't figure out how to turn #{obj.inspect} into CompiledJson"
14
15
  end
16
+ # :nocov:
15
17
  end
16
18
 
17
19
  def initialize(json, h = nil)
@@ -19,7 +21,7 @@ module JSONAPI
19
21
  @h = h
20
22
  end
21
23
 
22
- def to_json(*args)
24
+ def to_json(*_args)
23
25
  @json
24
26
  end
25
27
 
@@ -27,9 +29,17 @@ module JSONAPI
27
29
  @json
28
30
  end
29
31
 
32
+ # :nocov:
30
33
  def to_h
31
34
  @h ||= JSON.parse(@json)
32
35
  end
36
+ # :nocov:
37
+
38
+ def [](key)
39
+ # :nocov:
40
+ to_h[key]
41
+ # :nocov:
42
+ end
33
43
 
34
44
  undef_method :as_json
35
45
  end
@@ -8,13 +8,17 @@ module JSONAPI
8
8
  :resource_key_type,
9
9
  :route_format,
10
10
  :raise_if_parameters_not_allowed,
11
- :allow_include,
11
+ :warn_on_route_setup_issues,
12
+ :warn_on_missing_routes,
13
+ :warn_on_performance_issues,
14
+ :default_allow_include_to_one,
15
+ :default_allow_include_to_many,
12
16
  :allow_sort,
13
17
  :allow_filter,
14
18
  :default_paginator,
15
19
  :default_page_size,
16
20
  :maximum_page_size,
17
- :default_processor_klass,
21
+ :default_processor_klass_name,
18
22
  :use_text_errors,
19
23
  :top_level_links_include_pagination,
20
24
  :top_level_meta_include_record_count,
@@ -23,6 +27,7 @@ module JSONAPI
23
27
  :top_level_meta_page_count_key,
24
28
  :allow_transactions,
25
29
  :include_backtraces_in_errors,
30
+ :include_application_backtraces_in_errors,
26
31
  :exception_class_whitelist,
27
32
  :whitelist_all_exceptions,
28
33
  :always_include_to_one_linkage_data,
@@ -30,9 +35,12 @@ module JSONAPI
30
35
  :cache_formatters,
31
36
  :use_relationship_reflection,
32
37
  :resource_cache,
38
+ :default_caching,
33
39
  :default_resource_cache_field,
34
40
  :resource_cache_digest_function,
35
- :resource_cache_usage_report_function
41
+ :resource_cache_usage_report_function,
42
+ :default_exclude_links,
43
+ :use_related_resource_records_for_joins
36
44
 
37
45
  def initialize
38
46
  #:underscored_key, :camelized_key, :dasherized_key, or custom
@@ -45,12 +53,17 @@ module JSONAPI
45
53
  self.resource_key_type = :integer
46
54
 
47
55
  # optional request features
48
- self.allow_include = true
56
+ self.default_allow_include_to_one = true
57
+ self.default_allow_include_to_many = true
49
58
  self.allow_sort = true
50
59
  self.allow_filter = true
51
60
 
52
61
  self.raise_if_parameters_not_allowed = true
53
62
 
63
+ self.warn_on_route_setup_issues = true
64
+ self.warn_on_missing_routes = true
65
+ self.warn_on_performance_issues = true
66
+
54
67
  # :none, :offset, :paged, or a custom paginator name
55
68
  self.default_paginator = :none
56
69
 
@@ -71,8 +84,12 @@ module JSONAPI
71
84
  self.use_text_errors = false
72
85
 
73
86
  # Whether or not to include exception backtraces in JSONAPI error
74
- # responses. Defaults to `false` in production, and `true` otherwise.
75
- self.include_backtraces_in_errors = !Rails.env.production?
87
+ # responses. Defaults to `false` in anything other than development or test.
88
+ self.include_backtraces_in_errors = (Rails.env.development? || Rails.env.test?)
89
+
90
+ # Whether or not to include exception application backtraces in JSONAPI error
91
+ # responses. Defaults to `false` in anything other than development or test.
92
+ self.include_application_backtraces_in_errors = (Rails.env.development? || Rails.env.test?)
76
93
 
77
94
  # List of classes that should not be rescued by the operations processor.
78
95
  # For example, if you use Pundit for authorization, you might
@@ -94,7 +111,7 @@ module JSONAPI
94
111
 
95
112
  # The default Operation Processor to use if one is not defined specifically
96
113
  # for a Resource.
97
- self.default_processor_klass = JSONAPI::Processor
114
+ self.default_processor_klass_name = 'JSONAPI::Processor'
98
115
 
99
116
  # Allows transactions for creating and updating records
100
117
  # Set this to false if your backend does not support transactions (e.g. Mongodb)
@@ -117,6 +134,11 @@ module JSONAPI
117
134
  # Rails cache store.
118
135
  self.resource_cache = nil
119
136
 
137
+ # Cache resources by default
138
+ # Cache resources by default. Individual resources can be excluded from caching by calling:
139
+ # `caching false`
140
+ self.default_caching = false
141
+
120
142
  # Default resource cache field
121
143
  # On Resources with caching enabled, this field will be used to check for out-of-date
122
144
  # cache entries, unless overridden on a specific Resource. Defaults to "updated_at".
@@ -131,6 +153,17 @@ module JSONAPI
131
153
  # Optionally provide a callable which JSONAPI will call with information about cache
132
154
  # performance. Should accept three arguments: resource name, hits count, misses count.
133
155
  self.resource_cache_usage_report_function = nil
156
+
157
+ # Global configuration for links exclusion
158
+ # Controls whether to generate links like `self`, `related` with all the resources
159
+ # and relationships. Accepts either `:default`, `:none`, or array containing the
160
+ # specific default links to exclude, which may be `:self` and `:related`.
161
+ self.default_exclude_links = :none
162
+
163
+ # Use a related resource's `records` when performing joins. This setting allows included resources to account for
164
+ # permission scopes. It can be overridden explicitly per relationship. Furthermore, specifying a `relation_name`
165
+ # on a relationship will cause this setting to be ignored.
166
+ self.use_related_resource_records_for_joins = true
134
167
  end
135
168
 
136
169
  def cache_formatters=(bool)
@@ -198,10 +231,26 @@ module JSONAPI
198
231
  end
199
232
 
200
233
  def default_processor_klass=(default_processor_klass)
234
+ ActiveSupport::Deprecation.warn('`default_processor_klass` has been replaced by `default_processor_klass_name`.')
201
235
  @default_processor_klass = default_processor_klass
202
236
  end
203
237
 
204
- attr_writer :allow_include, :allow_sort, :allow_filter
238
+ def default_processor_klass
239
+ @default_processor_klass ||= default_processor_klass_name.safe_constantize
240
+ end
241
+
242
+ def default_processor_klass_name=(default_processor_klass_name)
243
+ @default_processor_klass = nil
244
+ @default_processor_klass_name = default_processor_klass_name
245
+ end
246
+
247
+ def allow_include=(allow_include)
248
+ ActiveSupport::Deprecation.warn('`allow_include` has been replaced by `default_allow_include_to_one` and `default_allow_include_to_many` options.')
249
+ @default_allow_include_to_one = allow_include
250
+ @default_allow_include_to_many = allow_include
251
+ end
252
+
253
+ attr_writer :allow_sort, :allow_filter, :default_allow_include_to_one, :default_allow_include_to_many
205
254
 
206
255
  attr_writer :default_paginator
207
256
 
@@ -225,6 +274,8 @@ module JSONAPI
225
274
 
226
275
  attr_writer :include_backtraces_in_errors
227
276
 
277
+ attr_writer :include_application_backtraces_in_errors
278
+
228
279
  attr_writer :exception_class_whitelist
229
280
 
230
281
  attr_writer :whitelist_all_exceptions
@@ -235,15 +286,27 @@ module JSONAPI
235
286
 
236
287
  attr_writer :raise_if_parameters_not_allowed
237
288
 
289
+ attr_writer :warn_on_route_setup_issues
290
+
291
+ attr_writer :warn_on_missing_routes
292
+
293
+ attr_writer :warn_on_performance_issues
294
+
238
295
  attr_writer :use_relationship_reflection
239
296
 
240
297
  attr_writer :resource_cache
241
298
 
299
+ attr_writer :default_caching
300
+
242
301
  attr_writer :default_resource_cache_field
243
302
 
244
303
  attr_writer :resource_cache_digest_function
245
304
 
246
305
  attr_writer :resource_cache_usage_report_function
306
+
307
+ attr_writer :default_exclude_links
308
+
309
+ attr_writer :use_related_resource_records_for_joins
247
310
  end
248
311
 
249
312
  class << self
data/lib/jsonapi/error.rb CHANGED
@@ -24,6 +24,33 @@ module JSONAPI
24
24
  instance_variables.each {|var| hash[var.to_s.delete('@')] = instance_variable_get(var) unless instance_variable_get(var).nil? }
25
25
  hash
26
26
  end
27
+
28
+ def update_with_overrides(error_object_overrides)
29
+ @title = error_object_overrides[:title] || @title
30
+ @detail = error_object_overrides[:detail] || @detail
31
+ @id = error_object_overrides[:id] || @id
32
+ @href = error_object_overrides[:href] || href
33
+
34
+ if error_object_overrides[:code]
35
+ # :nocov:
36
+ @code = if JSONAPI.configuration.use_text_errors
37
+ TEXT_ERRORS[error_object_overrides[:code]]
38
+ else
39
+ error_object_overrides[:code]
40
+ end
41
+ # :nocov:
42
+ end
43
+
44
+ @source = error_object_overrides[:source] || @source
45
+ @links = error_object_overrides[:links] || @links
46
+
47
+ if error_object_overrides[:status]
48
+ # :nocov:
49
+ @status = Rack::Utils::SYMBOL_TO_STATUS_CODE[error_object_overrides[:status]].to_s
50
+ # :nocov:
51
+ end
52
+ @meta = error_object_overrides[:meta] || @meta
53
+ end
27
54
  end
28
55
 
29
56
  class Warning
@@ -20,6 +20,7 @@ module JSONAPI
20
20
  INVALID_FILTERS_SYNTAX = '120'
21
21
  SAVE_FAILED = '121'
22
22
  INVALID_DATA_FORMAT = '122'
23
+ INVALID_RELATIONSHIP = '123'
23
24
  BAD_REQUEST = '400'
24
25
  FORBIDDEN = '403'
25
26
  RECORD_NOT_FOUND = '404'
@@ -50,6 +51,7 @@ module JSONAPI
50
51
  INVALID_FILTERS_SYNTAX => 'INVALID_FILTERS_SYNTAX',
51
52
  SAVE_FAILED => 'SAVE_FAILED',
52
53
  INVALID_DATA_FORMAT => 'INVALID_DATA_FORMAT',
54
+ INVALID_RELATIONSHIP => 'INVALID_RELATIONSHIP',
53
55
  FORBIDDEN => 'FORBIDDEN',
54
56
  RECORD_NOT_FOUND => 'RECORD_NOT_FOUND',
55
57
  NOT_ACCEPTABLE => 'NOT_ACCEPTABLE',
@@ -1,7 +1,7 @@
1
1
  module JSONAPI
2
2
  module Exceptions
3
3
  class Error < RuntimeError
4
- attr :error_object_overrides
4
+ attr_reader :error_object_overrides
5
5
 
6
6
  def initialize(error_object_overrides = {})
7
7
  @error_object_overrides = error_object_overrides
@@ -18,6 +18,22 @@ module JSONAPI
18
18
  end
19
19
  end
20
20
 
21
+ class Errors < Error
22
+ def initialize(errors, error_object_overrides = {})
23
+ @errors = errors
24
+
25
+ @errors.each do |error|
26
+ error.update_with_overrides(error_object_overrides)
27
+ end
28
+
29
+ super(error_object_overrides)
30
+ end
31
+
32
+ def errors
33
+ @errors
34
+ end
35
+ end
36
+
21
37
  class InternalServerError < Error
22
38
  attr_accessor :exception
23
39
 
@@ -33,6 +49,12 @@ module JSONAPI
33
49
  meta[:backtrace] = exception.backtrace
34
50
  end
35
51
 
52
+ if JSONAPI.configuration.include_application_backtraces_in_errors
53
+ meta ||= Hash.new
54
+ meta[:exception] ||= exception.message
55
+ meta[:application_backtrace] = exception.backtrace.select{|line| line =~ /#{Rails.root}/}
56
+ end
57
+
36
58
  [create_error_object(code: JSONAPI::INTERNAL_SERVER_ERROR,
37
59
  status: :internal_server_error,
38
60
  title: I18n.t('jsonapi-resources.exceptions.internal_server_error.title',
@@ -119,49 +141,30 @@ module JSONAPI
119
141
  end
120
142
  end
121
143
 
122
-
123
- class HasManyRelationExists < Error
124
- attr_accessor :id
125
-
126
- def initialize(id, error_object_overrides = {})
127
- @id = id
128
- super(error_object_overrides)
129
- end
130
-
131
- def errors
132
- [create_error_object(code: JSONAPI::RELATION_EXISTS,
133
- status: :bad_request,
134
- title: I18n.translate('jsonapi-resources.exceptions.has_many_relation.title',
135
- default: 'Relation exists'),
136
- detail: I18n.translate('jsonapi-resources.exceptions.has_many_relation.detail',
137
- default: "The relation to #{id} already exists.",
138
- id: id))]
139
- end
140
- end
141
-
142
144
  class BadRequest < Error
143
- def initialize(exception)
145
+ def initialize(exception, error_object_overrides = {})
144
146
  @exception = exception
147
+ super(error_object_overrides)
145
148
  end
146
149
 
147
150
  def errors
148
- [JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST,
149
- status: :bad_request,
150
- title: I18n.translate('jsonapi-resources.exceptions.bad_request.title',
151
- default: 'Bad Request'),
152
- detail: I18n.translate('jsonapi-resources.exceptions.bad_request.detail',
153
- default: @exception))]
151
+ [create_error_object(code: JSONAPI::BAD_REQUEST,
152
+ status: :bad_request,
153
+ title: I18n.translate('jsonapi-resources.exceptions.bad_request.title',
154
+ default: 'Bad Request'),
155
+ detail: I18n.translate('jsonapi-resources.exceptions.bad_request.detail',
156
+ default: @exception))]
154
157
  end
155
158
  end
156
159
 
157
160
  class InvalidRequestFormat < Error
158
161
  def errors
159
- [JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST,
160
- status: :bad_request,
161
- title: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.title',
162
- default: 'Bad Request'),
163
- detail: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.detail',
164
- default: 'Request must be a hash'))]
162
+ [create_error_object(code: JSONAPI::BAD_REQUEST,
163
+ status: :bad_request,
164
+ title: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.title',
165
+ default: 'Bad Request'),
166
+ detail: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.detail',
167
+ default: 'Request must be a hash'))]
165
168
  end
166
169
  end
167
170
 
@@ -324,6 +327,26 @@ module JSONAPI
324
327
  end
325
328
  end
326
329
 
330
+ class InvalidRelationship < Error
331
+ attr_accessor :relationship_name, :type
332
+
333
+ def initialize(type, relationship_name, error_object_overrides = {})
334
+ @relationship_name = relationship_name
335
+ @type = type
336
+ super(error_object_overrides)
337
+ end
338
+
339
+ def errors
340
+ [create_error_object(code: JSONAPI::INVALID_RELATIONSHIP,
341
+ status: :bad_request,
342
+ title: I18n.translate('jsonapi-resources.exceptions.invalid_relationship.title',
343
+ default: 'Invalid relationship'),
344
+ detail: I18n.translate('jsonapi-resources.exceptions.invalid_relationship.detail',
345
+ default: "#{relationship_name} is not a valid field for #{type}.",
346
+ relationship_name: relationship_name, type: type))]
347
+ end
348
+ end
349
+
327
350
  class InvalidInclude < Error
328
351
  attr_accessor :relationship, :resource
329
352
 
@@ -339,7 +362,7 @@ module JSONAPI
339
362
  title: I18n.translate('jsonapi-resources.exceptions.invalid_include.title',
340
363
  default: 'Invalid field'),
341
364
  detail: I18n.translate('jsonapi-resources.exceptions.invalid_include.detail',
342
- default: "#{relationship} is not a valid relationship of #{resource}",
365
+ default: "#{relationship} is not a valid includable relationship of #{resource}",
343
366
  relationship: relationship, resource: resource))]
344
367
  end
345
368
  end
@@ -364,24 +387,21 @@ module JSONAPI
364
387
  end
365
388
  end
366
389
 
367
- class ParametersNotAllowed < Error
368
- attr_accessor :params
390
+ class ParameterNotAllowed < Error
391
+ attr_accessor :param
369
392
 
370
- def initialize(params, error_object_overrides = {})
371
- @params = params
393
+ def initialize(param, error_object_overrides = {})
394
+ @param = param
372
395
  super(error_object_overrides)
373
396
  end
374
397
 
375
398
  def errors
376
- params.collect do |param|
377
- create_error_object(code: JSONAPI::PARAM_NOT_ALLOWED,
378
- status: :bad_request,
379
- title: I18n.translate('jsonapi-resources.exceptions.parameters_not_allowed.title',
380
- default: 'Param not allowed'),
381
- detail: I18n.translate('jsonapi-resources.exceptions.parameters_not_allowed.detail',
382
- default: "#{param} is not allowed.", param: param))
383
-
384
- end
399
+ [create_error_object(code: JSONAPI::PARAM_NOT_ALLOWED,
400
+ status: :bad_request,
401
+ title: I18n.translate('jsonapi-resources.exceptions.parameter_not_allowed.title',
402
+ default: 'Param not allowed'),
403
+ detail: I18n.translate('jsonapi-resources.exceptions.parameters_not_allowed.detail',
404
+ default: "#{param} is not allowed.", param: param))]
385
405
  end
386
406
  end
387
407
 
@@ -451,11 +471,12 @@ module JSONAPI
451
471
  end
452
472
 
453
473
  class ValidationErrors < Error
454
- attr_reader :error_messages, :error_metadata, :resource_relationships
474
+ attr_reader :error_messages, :error_metadata, :resource_relationships, :resource_class
455
475
 
456
476
  def initialize(resource, error_object_overrides = {})
457
477
  @error_messages = resource.model_error_messages
458
478
  @error_metadata = resource.validation_error_metadata
479
+ @resource_class = resource.class
459
480
  @resource_relationships = resource.class._relationships.keys
460
481
  @key_formatter = JSONAPI.configuration.key_formatter
461
482
  super(error_object_overrides)
@@ -477,7 +498,7 @@ module JSONAPI
477
498
  create_error_object(code: JSONAPI::VALIDATION_ERROR,
478
499
  status: :unprocessable_entity,
479
500
  title: message,
480
- detail: "#{format_key(attr_key)} - #{message}",
501
+ detail: detail(attr_key, message),
481
502
  source: { pointer: pointer(attr_key) },
482
503
  meta: metadata_for(attr_key, message))
483
504
  end
@@ -487,7 +508,12 @@ module JSONAPI
487
508
  error_metadata[attr_key] ? error_metadata[attr_key][message] : nil
488
509
  end
489
510
 
511
+ def detail(attr_key, message)
512
+ general_error?(attr_key) ? message : "#{format_key(attr_key)} - #{message}"
513
+ end
514
+
490
515
  def pointer(attr_or_relationship_name)
516
+ return '/data' if general_error?(attr_or_relationship_name)
491
517
  formatted_attr_or_relationship_name = format_key(attr_or_relationship_name)
492
518
  if resource_relationships.include?(attr_or_relationship_name)
493
519
  "/data/relationships/#{formatted_attr_or_relationship_name}"
@@ -495,6 +521,10 @@ module JSONAPI
495
521
  "/data/attributes/#{formatted_attr_or_relationship_name}"
496
522
  end
497
523
  end
524
+
525
+ def general_error?(attr_key)
526
+ attr_key.to_sym == :base && !resource_class._has_attribute?(attr_key)
527
+ end
498
528
  end
499
529
 
500
530
  class SaveFailed < Error
@@ -108,7 +108,7 @@ end
108
108
 
109
109
  class DasherizedKeyFormatter < JSONAPI::KeyFormatter
110
110
  class << self
111
- def format(key)
111
+ def format(_key)
112
112
  super.underscore.dasherize
113
113
  end
114
114
 
@@ -146,7 +146,7 @@ end
146
146
 
147
147
  class CamelizedRouteFormatter < JSONAPI::RouteFormatter
148
148
  class << self
149
- def format(route)
149
+ def format(_route)
150
150
  super.camelize(:lower)
151
151
  end
152
152
 
@@ -158,7 +158,7 @@ end
158
158
 
159
159
  class DasherizedRouteFormatter < JSONAPI::RouteFormatter
160
160
  class << self
161
- def format(route)
161
+ def format(_route)
162
162
  super.dasherize
163
163
  end
164
164