jsonapi-resources 0.9.12 → 0.10.0

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 +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 +297 -0
  8. data/lib/jsonapi/active_relation_resource.rb +836 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +123 -107
  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 +36 -5
  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/path.rb +43 -0
  23. data/lib/jsonapi/path_segment.rb +76 -0
  24. data/lib/jsonapi/processor.rb +234 -108
  25. data/lib/jsonapi/relationship.rb +108 -24
  26. data/lib/jsonapi/request_parser.rb +383 -396
  27. data/lib/jsonapi/resource.rb +3 -1376
  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 +124 -286
  33. data/lib/jsonapi/resource_set.rb +177 -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 +104 -87
  37. data/lib/jsonapi/routing_ext.rb +19 -21
  38. data/lib/jsonapi-resources.rb +20 -4
  39. data/lib/tasks/check_upgrade.rake +52 -0
  40. metadata +32 -29
  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,8 +8,11 @@ module JSONAPI
8
8
  :resource_key_type,
9
9
  :route_format,
10
10
  :raise_if_parameters_not_allowed,
11
+ :warn_on_route_setup_issues,
11
12
  :warn_on_missing_routes,
12
- :allow_include,
13
+ :warn_on_performance_issues,
14
+ :default_allow_include_to_one,
15
+ :default_allow_include_to_many,
13
16
  :allow_sort,
14
17
  :allow_filter,
15
18
  :default_paginator,
@@ -24,6 +27,7 @@ module JSONAPI
24
27
  :top_level_meta_page_count_key,
25
28
  :allow_transactions,
26
29
  :include_backtraces_in_errors,
30
+ :include_application_backtraces_in_errors,
27
31
  :exception_class_whitelist,
28
32
  :whitelist_all_exceptions,
29
33
  :always_include_to_one_linkage_data,
@@ -31,6 +35,7 @@ module JSONAPI
31
35
  :cache_formatters,
32
36
  :use_relationship_reflection,
33
37
  :resource_cache,
38
+ :default_caching,
34
39
  :default_resource_cache_field,
35
40
  :resource_cache_digest_function,
36
41
  :resource_cache_usage_report_function,
@@ -47,13 +52,16 @@ module JSONAPI
47
52
  self.resource_key_type = :integer
48
53
 
49
54
  # optional request features
50
- self.allow_include = true
55
+ self.default_allow_include_to_one = true
56
+ self.default_allow_include_to_many = true
51
57
  self.allow_sort = true
52
58
  self.allow_filter = true
53
59
 
54
60
  self.raise_if_parameters_not_allowed = true
55
61
 
62
+ self.warn_on_route_setup_issues = true
56
63
  self.warn_on_missing_routes = true
64
+ self.warn_on_performance_issues = true
57
65
 
58
66
  # :none, :offset, :paged, or a custom paginator name
59
67
  self.default_paginator = :none
@@ -75,8 +83,12 @@ module JSONAPI
75
83
  self.use_text_errors = false
76
84
 
77
85
  # Whether or not to include exception backtraces in JSONAPI error
78
- # responses. Defaults to `false` in production, and `true` otherwise.
79
- 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?)
80
92
 
81
93
  # List of classes that should not be rescued by the operations processor.
82
94
  # For example, if you use Pundit for authorization, you might
@@ -121,6 +133,11 @@ module JSONAPI
121
133
  # Rails cache store.
122
134
  self.resource_cache = nil
123
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
+
124
141
  # Default resource cache field
125
142
  # On Resources with caching enabled, this field will be used to check for out-of-date
126
143
  # cache entries, unless overridden on a specific Resource. Defaults to "updated_at".
@@ -211,7 +228,13 @@ module JSONAPI
211
228
  @default_processor_klass = default_processor_klass
212
229
  end
213
230
 
214
- attr_writer :allow_include, :allow_sort, :allow_filter
231
+ def allow_include=(allow_include)
232
+ ActiveSupport::Deprecation.warn('`allow_include` has been replaced by `default_allow_include_to_one` and `default_allow_include_to_many` options.')
233
+ @default_allow_include_to_one = allow_include
234
+ @default_allow_include_to_many = allow_include
235
+ end
236
+
237
+ attr_writer :allow_sort, :allow_filter, :default_allow_include_to_one, :default_allow_include_to_many
215
238
 
216
239
  attr_writer :default_paginator
217
240
 
@@ -235,6 +258,8 @@ module JSONAPI
235
258
 
236
259
  attr_writer :include_backtraces_in_errors
237
260
 
261
+ attr_writer :include_application_backtraces_in_errors
262
+
238
263
  attr_writer :exception_class_whitelist
239
264
 
240
265
  attr_writer :whitelist_all_exceptions
@@ -245,12 +270,18 @@ module JSONAPI
245
270
 
246
271
  attr_writer :raise_if_parameters_not_allowed
247
272
 
273
+ attr_writer :warn_on_route_setup_issues
274
+
248
275
  attr_writer :warn_on_missing_routes
249
276
 
277
+ attr_writer :warn_on_performance_issues
278
+
250
279
  attr_writer :use_relationship_reflection
251
280
 
252
281
  attr_writer :resource_cache
253
282
 
283
+ attr_writer :default_caching
284
+
254
285
  attr_writer :default_resource_cache_field
255
286
 
256
287
  attr_writer :resource_cache_digest_function
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,79 +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
- def merge_filter(relation, filter)
44
- config = include_config(relation.to_sym)
45
- config[:include_filters] ||= {}
46
- config[:include_filters].merge!(filter)
47
- end
48
-
49
- def include_config(relation)
50
- @include_directives_hash[:include_related][relation]
51
- end
52
-
53
32
  private
54
33
 
55
- def get_related(current_path)
56
- current = @include_directives_hash
57
- current_resource_klass = @resource_klass
58
- current_path.split('.').each do |fragment|
59
- fragment = fragment.to_sym
60
-
61
- if current_resource_klass
62
- current_relationship = current_resource_klass._relationships[fragment]
63
- current_resource_klass = current_relationship.try(:resource_klass)
64
- else
65
- raise JSONAPI::Exceptions::InvalidInclude.new(current_resource_klass, current_path)
66
- end
67
-
68
- include_in_join = @force_eager_load || !current_relationship || current_relationship.eager_load_on_include
69
-
70
- current[:include_related][fragment] ||= { include: false, include_related: {}, include_in_join: include_in_join }
71
- current = current[:include_related][fragment]
72
- end
73
- current
74
- end
75
-
76
- def get_includes(directive, only_joined_includes = true)
77
- ir = directive[:include_related]
78
- 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)
79
39
 
80
- ir.map do |name, sub_directive|
81
- sub = get_includes(sub_directive, only_joined_includes)
82
- sub.any? ? { name => sub } : name
83
- end
84
- end
40
+ current = @include_directives_hash
85
41
 
86
- def parse_include(include)
87
- parts = include.split('.')
88
- local_path = ''
42
+ path.segments.each do |segment|
43
+ relationship_name = segment.relationship.name.to_sym
89
44
 
90
- parts.each do |name|
91
- local_path += local_path.length > 0 ? ".#{name}" : name
92
- related = get_related(local_path)
93
- related[:include] = true
45
+ current[:include_related][relationship_name] ||= { include_related: {} }
46
+ current = current[:include_related][relationship_name]
94
47
  end
95
- end
96
48
 
97
- def delve_paths(obj)
98
- case obj
99
- when Array
100
- obj.map{|elem| delve_paths(elem)}.flatten(1)
101
- when Hash
102
- obj.map{|k,v| [[k]] + delve_paths(v).map{|path| [k] + path } }.flatten(1)
103
- when Symbol, String
104
- [[obj]]
105
- else
106
- raise "delve_paths cannot descend into #{obj.class.name}"
107
- end
49
+ rescue JSONAPI::Exceptions::InvalidRelationship => _e
50
+ raise JSONAPI::Exceptions::InvalidInclude.new(@resource_klass, include)
108
51
  end
109
52
  end
110
53
  end