jsonapi-resources 0.9.0 → 0.10.6

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 +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