jsonapi-resources 0.9.3 → 0.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +1 -1
  3. data/README.md +34 -11
  4. data/lib/bug_report_templates/rails_5_latest.rb +125 -0
  5. data/lib/bug_report_templates/rails_5_master.rb +140 -0
  6. data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +27 -0
  7. data/lib/jsonapi/active_relation/join_manager.rb +297 -0
  8. data/lib/jsonapi/active_relation_resource.rb +879 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +122 -105
  10. data/lib/jsonapi/basic_resource.rb +1162 -0
  11. data/lib/jsonapi/cached_response_fragment.rb +127 -0
  12. data/lib/jsonapi/compiled_json.rb +11 -1
  13. data/lib/jsonapi/configuration.rb +63 -8
  14. data/lib/jsonapi/error.rb +27 -0
  15. data/lib/jsonapi/error_codes.rb +2 -0
  16. data/lib/jsonapi/exceptions.rb +63 -40
  17. data/lib/jsonapi/formatter.rb +3 -3
  18. data/lib/jsonapi/include_directives.rb +18 -65
  19. data/lib/jsonapi/link_builder.rb +74 -80
  20. data/lib/jsonapi/operation.rb +16 -5
  21. data/lib/jsonapi/operation_result.rb +74 -16
  22. data/lib/jsonapi/path.rb +43 -0
  23. data/lib/jsonapi/path_segment.rb +76 -0
  24. data/lib/jsonapi/processor.rb +237 -110
  25. data/lib/jsonapi/relationship.rb +144 -15
  26. data/lib/jsonapi/request_parser.rb +412 -357
  27. data/lib/jsonapi/resource.rb +3 -1263
  28. data/lib/jsonapi/resource_controller_metal.rb +5 -2
  29. data/lib/jsonapi/resource_fragment.rb +47 -0
  30. data/lib/jsonapi/resource_id_tree.rb +112 -0
  31. data/lib/jsonapi/resource_identity.rb +42 -0
  32. data/lib/jsonapi/resource_serializer.rb +143 -285
  33. data/lib/jsonapi/resource_set.rb +176 -0
  34. data/lib/jsonapi/resources/railtie.rb +9 -0
  35. data/lib/jsonapi/resources/version.rb +1 -1
  36. data/lib/jsonapi/response_document.rb +105 -83
  37. data/lib/jsonapi/routing_ext.rb +48 -26
  38. data/lib/jsonapi-resources.rb +20 -4
  39. data/lib/tasks/check_upgrade.rake +52 -0
  40. metadata +47 -17
  41. data/lib/jsonapi/cached_resource_fragment.rb +0 -127
  42. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  43. data/lib/jsonapi/operation_results.rb +0 -35
  44. data/lib/jsonapi/relationship_builder.rb +0 -167
@@ -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
+ @relationships = relationships
55
+
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,11 @@ 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
36
43
 
37
44
  def initialize
38
45
  #:underscored_key, :camelized_key, :dasherized_key, or custom
@@ -45,12 +52,17 @@ module JSONAPI
45
52
  self.resource_key_type = :integer
46
53
 
47
54
  # optional request features
48
- self.allow_include = true
55
+ self.default_allow_include_to_one = true
56
+ self.default_allow_include_to_many = true
49
57
  self.allow_sort = true
50
58
  self.allow_filter = true
51
59
 
52
60
  self.raise_if_parameters_not_allowed = true
53
61
 
62
+ self.warn_on_route_setup_issues = true
63
+ self.warn_on_missing_routes = true
64
+ self.warn_on_performance_issues = true
65
+
54
66
  # :none, :offset, :paged, or a custom paginator name
55
67
  self.default_paginator = :none
56
68
 
@@ -71,8 +83,12 @@ module JSONAPI
71
83
  self.use_text_errors = false
72
84
 
73
85
  # 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?
86
+ # responses. Defaults to `false` in anything other than development or test.
87
+ self.include_backtraces_in_errors = (Rails.env.development? || Rails.env.test?)
88
+
89
+ # Whether or not to include exception application backtraces in JSONAPI error
90
+ # responses. Defaults to `false` in anything other than development or test.
91
+ self.include_application_backtraces_in_errors = (Rails.env.development? || Rails.env.test?)
76
92
 
77
93
  # List of classes that should not be rescued by the operations processor.
78
94
  # For example, if you use Pundit for authorization, you might
@@ -94,7 +110,7 @@ module JSONAPI
94
110
 
95
111
  # The default Operation Processor to use if one is not defined specifically
96
112
  # for a Resource.
97
- self.default_processor_klass = JSONAPI::Processor
113
+ self.default_processor_klass_name = 'JSONAPI::Processor'
98
114
 
99
115
  # Allows transactions for creating and updating records
100
116
  # Set this to false if your backend does not support transactions (e.g. Mongodb)
@@ -117,6 +133,11 @@ module JSONAPI
117
133
  # Rails cache store.
118
134
  self.resource_cache = nil
119
135
 
136
+ # Cache resources by default
137
+ # Cache resources by default. Individual resources can be excluded from caching by calling:
138
+ # `caching false`
139
+ self.default_caching = false
140
+
120
141
  # Default resource cache field
121
142
  # On Resources with caching enabled, this field will be used to check for out-of-date
122
143
  # cache entries, unless overridden on a specific Resource. Defaults to "updated_at".
@@ -131,6 +152,12 @@ module JSONAPI
131
152
  # Optionally provide a callable which JSONAPI will call with information about cache
132
153
  # performance. Should accept three arguments: resource name, hits count, misses count.
133
154
  self.resource_cache_usage_report_function = nil
155
+
156
+ # Global configuration for links exclusion
157
+ # Controls whether to generate links like `self`, `related` with all the resources
158
+ # and relationships. Accepts either `:default`, `:none`, or array containing the
159
+ # specific default links to exclude, which may be `:self` and `:related`.
160
+ self.default_exclude_links = :none
134
161
  end
135
162
 
136
163
  def cache_formatters=(bool)
@@ -198,10 +225,26 @@ module JSONAPI
198
225
  end
199
226
 
200
227
  def default_processor_klass=(default_processor_klass)
228
+ ActiveSupport::Deprecation.warn('`default_processor_klass` has been replaced by `default_processor_klass_name`.')
201
229
  @default_processor_klass = default_processor_klass
202
230
  end
203
231
 
204
- attr_writer :allow_include, :allow_sort, :allow_filter
232
+ def default_processor_klass
233
+ @default_processor_klass ||= default_processor_klass_name.safe_constantize
234
+ end
235
+
236
+ def default_processor_klass_name=(default_processor_klass_name)
237
+ @default_processor_klass = nil
238
+ @default_processor_klass_name = default_processor_klass_name
239
+ end
240
+
241
+ def allow_include=(allow_include)
242
+ ActiveSupport::Deprecation.warn('`allow_include` has been replaced by `default_allow_include_to_one` and `default_allow_include_to_many` options.')
243
+ @default_allow_include_to_one = allow_include
244
+ @default_allow_include_to_many = allow_include
245
+ end
246
+
247
+ attr_writer :allow_sort, :allow_filter, :default_allow_include_to_one, :default_allow_include_to_many
205
248
 
206
249
  attr_writer :default_paginator
207
250
 
@@ -225,6 +268,8 @@ module JSONAPI
225
268
 
226
269
  attr_writer :include_backtraces_in_errors
227
270
 
271
+ attr_writer :include_application_backtraces_in_errors
272
+
228
273
  attr_writer :exception_class_whitelist
229
274
 
230
275
  attr_writer :whitelist_all_exceptions
@@ -235,15 +280,25 @@ module JSONAPI
235
280
 
236
281
  attr_writer :raise_if_parameters_not_allowed
237
282
 
283
+ attr_writer :warn_on_route_setup_issues
284
+
285
+ attr_writer :warn_on_missing_routes
286
+
287
+ attr_writer :warn_on_performance_issues
288
+
238
289
  attr_writer :use_relationship_reflection
239
290
 
240
291
  attr_writer :resource_cache
241
292
 
293
+ attr_writer :default_caching
294
+
242
295
  attr_writer :default_resource_cache_field
243
296
 
244
297
  attr_writer :resource_cache_digest_function
245
298
 
246
299
  attr_writer :resource_cache_usage_report_function
300
+
301
+ attr_writer :default_exclude_links
247
302
  end
248
303
 
249
304
  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
@@ -374,11 +397,11 @@ module JSONAPI
374
397
 
375
398
  def errors
376
399
  [create_error_object(code: JSONAPI::PARAM_NOT_ALLOWED,
377
- status: :bad_request,
378
- title: I18n.translate('jsonapi-resources.exceptions.parameter_not_allowed.title',
379
- default: 'Param not allowed'),
380
- detail: I18n.translate('jsonapi-resources.exceptions.parameter_not_allowed.detail',
381
- default: "#{param} is not allowed.", param: param))]
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))]
382
405
  end
383
406
  end
384
407
 
@@ -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
 
@@ -4,14 +4,12 @@ module JSONAPI
4
4
  # For example ['posts.comments.tags']
5
5
  # will transform into =>
6
6
  # {
7
- # posts:{
8
- # include:true,
9
- # include_related:{
7
+ # posts: {
8
+ # include_related: {
10
9
  # comments:{
11
- # include:true,
12
- # include_related:{
13
- # tags:{
14
- # include:true
10
+ # include_related: {
11
+ # tags: {
12
+ # include_related: {}
15
13
  # }
16
14
  # }
17
15
  # }
@@ -19,9 +17,8 @@ module JSONAPI
19
17
  # }
20
18
  # }
21
19
 
22
- def initialize(resource_klass, includes_array, force_eager_load: false)
20
+ def initialize(resource_klass, includes_array)
23
21
  @resource_klass = resource_klass
24
- @force_eager_load = force_eager_load
25
22
  @include_directives_hash = { include_related: {} }
26
23
  includes_array.each do |include|
27
24
  parse_include(include)
@@ -32,69 +29,25 @@ module JSONAPI
32
29
  @include_directives_hash
33
30
  end
34
31
 
35
- def model_includes
36
- get_includes(@include_directives_hash)
37
- end
38
-
39
- def paths
40
- delve_paths(get_includes(@include_directives_hash, false))
41
- end
42
-
43
32
  private
44
33
 
45
- def get_related(current_path)
46
- current = @include_directives_hash
47
- current_resource_klass = @resource_klass
48
- current_path.split('.').each do |fragment|
49
- fragment = fragment.to_sym
50
-
51
- if current_resource_klass
52
- current_relationship = current_resource_klass._relationships[fragment]
53
- current_resource_klass = current_relationship.try(:resource_klass)
54
- else
55
- raise JSONAPI::Exceptions::InvalidInclude.new(current_resource_klass, current_path)
56
- end
57
-
58
- include_in_join = @force_eager_load || !current_relationship || current_relationship.eager_load_on_include
59
-
60
- current[:include_related][fragment] ||= { include: false, include_related: {}, include_in_join: include_in_join }
61
- current = current[:include_related][fragment]
62
- end
63
- current
64
- end
65
-
66
- def get_includes(directive, only_joined_includes = true)
67
- ir = directive[:include_related]
68
- ir = ir.select { |k,v| v[:include_in_join] } if only_joined_includes
34
+ def parse_include(include)
35
+ path = JSONAPI::Path.new(resource_klass: @resource_klass,
36
+ path_string: include,
37
+ ensure_default_field: false,
38
+ parse_fields: false)
69
39
 
70
- ir.map do |name, sub_directive|
71
- sub = get_includes(sub_directive, only_joined_includes)
72
- sub.any? ? { name => sub } : name
73
- end
74
- end
40
+ current = @include_directives_hash
75
41
 
76
- def parse_include(include)
77
- parts = include.split('.')
78
- local_path = ''
42
+ path.segments.each do |segment|
43
+ relationship_name = segment.relationship.name.to_sym
79
44
 
80
- parts.each do |name|
81
- local_path += local_path.length > 0 ? ".#{name}" : name
82
- related = get_related(local_path)
83
- related[:include] = true
45
+ current[:include_related][relationship_name] ||= { include_related: {} }
46
+ current = current[:include_related][relationship_name]
84
47
  end
85
- end
86
48
 
87
- def delve_paths(obj)
88
- case obj
89
- when Array
90
- obj.map{|elem| delve_paths(elem)}.flatten(1)
91
- when Hash
92
- obj.map{|k,v| [[k]] + delve_paths(v).map{|path| [k] + path } }.flatten(1)
93
- when Symbol, String
94
- [[obj]]
95
- else
96
- raise "delve_paths cannot descend into #{obj.class.name}"
97
- end
49
+ rescue JSONAPI::Exceptions::InvalidRelationship => _e
50
+ raise JSONAPI::Exceptions::InvalidInclude.new(@resource_klass, include)
98
51
  end
99
52
  end
100
53
  end