jsonapi-resources 0.10.6 → 0.11.0.beta2

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +39 -2
  4. data/lib/generators/jsonapi/controller_generator.rb +2 -0
  5. data/lib/generators/jsonapi/resource_generator.rb +2 -0
  6. data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +3 -2
  7. data/lib/jsonapi/active_relation/join_manager.rb +30 -18
  8. data/lib/jsonapi/active_relation/join_manager_v10.rb +305 -0
  9. data/lib/jsonapi/active_relation_retrieval.rb +885 -0
  10. data/lib/jsonapi/active_relation_retrieval_v09.rb +715 -0
  11. data/lib/jsonapi/{active_relation_resource.rb → active_relation_retrieval_v10.rb} +113 -135
  12. data/lib/jsonapi/acts_as_resource_controller.rb +49 -49
  13. data/lib/jsonapi/cached_response_fragment.rb +4 -2
  14. data/lib/jsonapi/callbacks.rb +2 -0
  15. data/lib/jsonapi/compiled_json.rb +2 -0
  16. data/lib/jsonapi/configuration.rb +35 -15
  17. data/lib/jsonapi/error.rb +2 -0
  18. data/lib/jsonapi/error_codes.rb +2 -0
  19. data/lib/jsonapi/exceptions.rb +2 -0
  20. data/lib/jsonapi/formatter.rb +2 -0
  21. data/lib/jsonapi/include_directives.rb +77 -19
  22. data/lib/jsonapi/link_builder.rb +2 -0
  23. data/lib/jsonapi/mime_types.rb +6 -10
  24. data/lib/jsonapi/naive_cache.rb +2 -0
  25. data/lib/jsonapi/operation.rb +2 -0
  26. data/lib/jsonapi/operation_result.rb +2 -0
  27. data/lib/jsonapi/paginator.rb +2 -0
  28. data/lib/jsonapi/path.rb +2 -0
  29. data/lib/jsonapi/path_segment.rb +4 -2
  30. data/lib/jsonapi/processor.rb +95 -140
  31. data/lib/jsonapi/relationship.rb +89 -35
  32. data/lib/jsonapi/{request_parser.rb → request.rb} +157 -164
  33. data/lib/jsonapi/resource.rb +7 -2
  34. data/lib/jsonapi/{basic_resource.rb → resource_common.rb} +187 -88
  35. data/lib/jsonapi/resource_controller.rb +2 -0
  36. data/lib/jsonapi/resource_controller_metal.rb +2 -0
  37. data/lib/jsonapi/resource_fragment.rb +17 -15
  38. data/lib/jsonapi/resource_identity.rb +6 -0
  39. data/lib/jsonapi/resource_serializer.rb +20 -4
  40. data/lib/jsonapi/resource_set.rb +36 -16
  41. data/lib/jsonapi/resource_tree.rb +191 -0
  42. data/lib/jsonapi/resources/railtie.rb +3 -1
  43. data/lib/jsonapi/resources/version.rb +3 -1
  44. data/lib/jsonapi/response_document.rb +4 -2
  45. data/lib/jsonapi/routing_ext.rb +4 -2
  46. data/lib/jsonapi/simple_resource.rb +13 -0
  47. data/lib/jsonapi-resources.rb +10 -4
  48. data/lib/tasks/check_upgrade.rake +3 -1
  49. metadata +47 -15
  50. data/lib/jsonapi/resource_id_tree.rb +0 -112
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '091836afd689ed975ab1ad2cee54a467a0771cb5ea5b0bfa2055e25531809f74'
4
- data.tar.gz: 96d46c67f8c88fa3e6d298eac85dc3862bef6feacbe8eddde1515b80e7c8e5a8
3
+ metadata.gz: 3b0c18d478ae14531738238de633c47aace50c6f7a2c3ff25e9ce09033e802ce
4
+ data.tar.gz: 8365f223895635d28c12ecf334193ed4f3a30368a3a4d674fc5e65366f3d317f
5
5
  SHA512:
6
- metadata.gz: f4dfc6fd61f4a7d27a291626b00c6e5c13cad32be0b73b66e239f254800d73557168470191923cd5bcf29869bfdb1e93a11c4a8b6e3eca25411b1ac8dbc4e24d
7
- data.tar.gz: e6c699748872330b22ae151082595e48bed10665e9c61c3ba902f585747090e19ab9319776d189bc06fe9ca7797f00467fb08a3e863a4ce7e3b740fd85bc2fe8
6
+ metadata.gz: d06fbbf825b5123ef5bef84de20f5a30ea7ea352bb82a0db56a13f84cc90ff289290c40928d7155adeabd78fc0ef4011013b0015bb6a49703364e9c4cb814eb9
7
+ data.tar.gz: c112c3947eb59fd53475ac8f9483a1754dd01677f1854ff44bc751b0d4adeb1cef5d49e99f6ef166af0335eef3f3e66719fd58cb4092723348a7aa2be3222677
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2017 Cerebris Corporation
1
+ Copyright (c) 2014-2023 Cerebris Corporation
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  Like JSON:API itself, JR's design is focused on the resources served by an API. JR needs little more than a definition
9
9
  of your resources, including their attributes and relationships, to make your server compliant with JSON API.
10
10
 
11
- JR is designed to work with Rails 4.2+, and provides custom routes, controllers, and serializers. JR's resources may be
11
+ JR is designed to work with Rails 5.1+, and provides custom routes, controllers, and serializers. JR's resources may be
12
12
  backed by ActiveRecord models or by custom objects.
13
13
 
14
14
  ## Documentation
@@ -46,6 +46,43 @@ gem install jsonapi-resources
46
46
 
47
47
  **For further usage see the [v0.10 alpha Guide](http://jsonapi-resources.com/v0.10/guide/)**
48
48
 
49
+ ## Development
50
+
51
+ There is a docker compose setup that can be used for testing your code against the supported versions of ruby and
52
+ against PostgreSQL, MYSQL, and SQLite databases.
53
+
54
+ First build the docker image:
55
+ ```bash
56
+ docker compose build
57
+ ```
58
+ Be sure to rebuild after making code changes. It should be fast after the initial build.
59
+
60
+ ### Running the tests
61
+
62
+ The default command will run everything (it may take a while):
63
+ ```bash
64
+ docker compose run tests
65
+ ```
66
+
67
+ To test just one database against the latest ruby:
68
+ ```bash
69
+ docker compose run tests ./test_mysql
70
+ docker compose run tests ./test_postgresql
71
+ docker compose run tests ./test_sqlite
72
+ ```
73
+
74
+ To test a version of ruby against all databases:
75
+ ```bash
76
+ docker compose run tests ./test_ruby 2.7.7
77
+ ```
78
+
79
+ The tests by default run against the latest rails version. To override that you can set the RAILS_VERSION environment
80
+ variable:
81
+
82
+ ```bash
83
+ docker compose run -e RAILS_VERSION=6.1.1 tests ./test_postgresql
84
+ ```
85
+
49
86
  ## Contributing
50
87
 
51
88
  1. Submit an issue describing any new features you wish it add or the bug you intend to fix
@@ -73,4 +110,4 @@ and **paste the content into the issue description or attach as a file**:
73
110
 
74
111
  ## License
75
112
 
76
- Copyright 2014-2017 Cerebris Corporation. MIT License (see LICENSE for details).
113
+ Copyright 2014-2023 Cerebris Corporation. MIT License (see LICENSE for details).
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jsonapi
2
4
  class ControllerGenerator < Rails::Generators::NamedBase
3
5
  source_root File.expand_path('../templates', __FILE__)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jsonapi
2
4
  class ResourceGenerator < Rails::Generators::NamedBase
3
5
  source_root File.expand_path('../templates', __FILE__)
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module ActiveRelation
3
5
  module Adapters
4
6
  module JoinLeftActiveRecordAdapter
5
-
6
7
  # Extends left_joins functionality to rails 4, and uses the same logic for rails 5.0.x and 5.1.x
7
8
  # The default left_joins logic of rails 5.2.x is used. This results in and extra join in some cases. For
8
9
  # example Post.joins(:comments).joins_left(comments: :author) will join the comments table twice,
@@ -24,4 +25,4 @@ module JSONAPI
24
25
  end
25
26
  end
26
27
  end
27
- end
28
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module ActiveRelation
3
5
 
@@ -7,17 +9,22 @@ module JSONAPI
7
9
  attr_reader :resource_klass,
8
10
  :source_relationship,
9
11
  :resource_join_tree,
10
- :join_details
12
+ :join_details,
13
+ :through_source
11
14
 
12
15
  def initialize(resource_klass:,
13
16
  source_relationship: nil,
17
+ source_resource_klass: nil,
18
+ through_source: false,
14
19
  relationships: nil,
15
20
  filters: nil,
16
21
  sort_criteria: nil)
17
22
 
18
23
  @resource_klass = resource_klass
24
+ @source_resource_klass = source_resource_klass
19
25
  @join_details = nil
20
26
  @collected_aliases = Set.new
27
+ @through_source = through_source
21
28
 
22
29
  @resource_join_tree = {
23
30
  root: {
@@ -45,7 +52,7 @@ module JSONAPI
45
52
  # this method gets the join details whether they are on a relationship or are just pseudo details for the base
46
53
  # resource. Specify the resource type for polymorphic relationships
47
54
  #
48
- def source_join_details(type=nil)
55
+ def source_join_details(type = nil)
49
56
  if source_relationship
50
57
  related_resource_klass = type ? resource_klass.resource_klass_for(type) : source_relationship.resource_klass
51
58
  segment = PathSegment::Relationship.new(relationship: source_relationship, resource_klass: related_resource_klass)
@@ -90,14 +97,20 @@ module JSONAPI
90
97
  end
91
98
 
92
99
  def self.alias_from_arel_node(node)
93
- case node.left
100
+ # case node.left
101
+ case node&.left
94
102
  when Arel::Table
95
103
  node.left.name
96
104
  when Arel::Nodes::TableAlias
97
105
  node.left.right
98
106
  when Arel::Nodes::StringJoin
99
107
  # :nocov:
100
- warn "alias_from_arel_node: Unsupported join type - use custom filtering and sorting"
108
+ warn "alias_from_arel_node: Unsupported join type `Arel::Nodes::StringJoin` - use custom filtering and sorting"
109
+ nil
110
+ # :nocov:
111
+ else
112
+ # :nocov:
113
+ warn "alias_from_arel_node: Unsupported join type `#{node&.left.to_s}`"
101
114
  nil
102
115
  # :nocov:
103
116
  end
@@ -147,15 +160,9 @@ module JSONAPI
147
160
  related_resource_klass = join_details[:related_resource_klass]
148
161
  join_type = relationship_details[:join_type]
149
162
 
150
- join_options = {
151
- relationship: relationship,
152
- relationship_details: relationship_details,
153
- related_resource_klass: related_resource_klass,
154
- }
155
-
156
163
  if relationship == :root
157
164
  unless source_relationship
158
- add_join_details('', {alias: resource_klass._table_name, join_type: :root, join_options: join_options})
165
+ add_join_details('', {alias: resource_klass._table_name, join_type: :root})
159
166
  end
160
167
  next
161
168
  end
@@ -169,7 +176,8 @@ module JSONAPI
169
176
  options: options)
170
177
  }
171
178
 
172
- details = {alias: self.class.alias_from_arel_node(join_node), join_type: join_type, join_options: join_options}
179
+ join_alias = self.class.alias_from_arel_node(join_node)
180
+ details = {alias: join_alias, join_type: join_type}
173
181
 
174
182
  if relationship == source_relationship
175
183
  if relationship.polymorphic? && relationship.belongs_to?
@@ -181,15 +189,19 @@ module JSONAPI
181
189
 
182
190
  # We're adding the source alias with two keys. We only want the check for duplicate aliases once.
183
191
  # See the note in `add_join_details`.
184
- check_for_duplicate_alias = !(relationship == source_relationship)
185
- add_join_details(PathSegment::Relationship.new(relationship: relationship, resource_klass: related_resource_klass), details, check_for_duplicate_alias)
192
+ check_for_duplicate_alias = relationship != source_relationship
193
+ path_segment = PathSegment::Relationship.new(relationship: relationship,
194
+ resource_klass: related_resource_klass)
195
+
196
+ add_join_details(path_segment, details, check_for_duplicate_alias)
186
197
  end
187
198
  end
188
199
  records
189
200
  end
190
201
 
191
202
  def add_join(path, default_type = :inner, default_polymorphic_join_type = :left)
192
- if source_relationship
203
+ # puts "add_join #{path} default_type=#{default_type} default_polymorphic_join_type=#{default_polymorphic_join_type}"
204
+ if source_relationship && through_source
193
205
  if source_relationship.polymorphic?
194
206
  # Polymorphic paths will come it with the resource_type as the first segment (for example `#documents.comments`)
195
207
  # We just need to prepend the relationship portion the
@@ -201,9 +213,9 @@ module JSONAPI
201
213
  sourced_path = path
202
214
  end
203
215
 
204
- join_manager, _field = parse_path_to_tree(sourced_path, resource_klass, default_type, default_polymorphic_join_type)
216
+ join_tree, _field = parse_path_to_tree(sourced_path, resource_klass, default_type, default_polymorphic_join_type)
205
217
 
206
- @resource_join_tree[:root].deep_merge!(join_manager) { |key, val, other_val|
218
+ @resource_join_tree[:root].deep_merge!(join_tree) { |key, val, other_val|
207
219
  if key == :join_type
208
220
  if val == other_val
209
221
  val
@@ -300,4 +312,4 @@ module JSONAPI
300
312
  end
301
313
  end
302
314
  end
303
- end
315
+ end
@@ -0,0 +1,305 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONAPI
4
+ module ActiveRelation
5
+
6
+ # Stores relationship paths starting from the resource_klass, consolidating duplicate paths from
7
+ # relationships, filters and sorts. When joins are made the table aliases are tracked in join_details
8
+ class JoinManagerV10
9
+ attr_reader :resource_klass,
10
+ :source_relationship,
11
+ :resource_join_tree,
12
+ :join_details
13
+
14
+ def initialize(resource_klass:,
15
+ source_relationship: nil,
16
+ relationships: nil,
17
+ filters: nil,
18
+ sort_criteria: nil)
19
+
20
+ @resource_klass = resource_klass
21
+ @join_details = nil
22
+ @collected_aliases = Set.new
23
+
24
+ @resource_join_tree = {
25
+ root: {
26
+ join_type: :root,
27
+ resource_klasses: {
28
+ resource_klass => {
29
+ relationships: {}
30
+ }
31
+ }
32
+ }
33
+ }
34
+ add_source_relationship(source_relationship)
35
+ add_sort_criteria(sort_criteria)
36
+ add_filters(filters)
37
+ add_relationships(relationships)
38
+ end
39
+
40
+ def join(records, options)
41
+ fail "can't be joined again" if @join_details
42
+ @join_details = {}
43
+ perform_joins(records, options)
44
+ end
45
+
46
+ # source details will only be on a relationship if the source_relationship is set
47
+ # this method gets the join details whether they are on a relationship or are just pseudo details for the base
48
+ # resource. Specify the resource type for polymorphic relationships
49
+ #
50
+ def source_join_details(type=nil)
51
+ if source_relationship
52
+ related_resource_klass = type ? resource_klass.resource_klass_for(type) : source_relationship.resource_klass
53
+ segment = PathSegment::Relationship.new(relationship: source_relationship, resource_klass: related_resource_klass)
54
+ details = @join_details[segment]
55
+ else
56
+ if type
57
+ details = @join_details["##{type}"]
58
+ else
59
+ details = @join_details['']
60
+ end
61
+ end
62
+ details
63
+ end
64
+
65
+ def join_details_by_polymorphic_relationship(relationship, type)
66
+ segment = PathSegment::Relationship.new(relationship: relationship, resource_klass: resource_klass.resource_klass_for(type))
67
+ @join_details[segment]
68
+ end
69
+
70
+ def join_details_by_relationship(relationship)
71
+ segment = PathSegment::Relationship.new(relationship: relationship, resource_klass: relationship.resource_klass)
72
+ @join_details[segment]
73
+ end
74
+
75
+ def self.get_join_arel_node(records, options = {})
76
+ init_join_sources = records.arel.join_sources
77
+ init_join_sources_length = init_join_sources.length
78
+
79
+ records = yield(records, options)
80
+
81
+ join_sources = records.arel.join_sources
82
+ if join_sources.length > init_join_sources_length
83
+ last_join = (join_sources - init_join_sources).last
84
+ else
85
+ # :nocov:
86
+ warn "get_join_arel_node: No join added"
87
+ last_join = nil
88
+ # :nocov:
89
+ end
90
+
91
+ return records, last_join
92
+ end
93
+
94
+ def self.alias_from_arel_node(node)
95
+ case node.left
96
+ when Arel::Table
97
+ node.left.name
98
+ when Arel::Nodes::TableAlias
99
+ node.left.right
100
+ when Arel::Nodes::StringJoin
101
+ # :nocov:
102
+ warn "alias_from_arel_node: Unsupported join type - use custom filtering and sorting"
103
+ nil
104
+ # :nocov:
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def flatten_join_tree_by_depth(join_array = [], node = @resource_join_tree, level = 0)
111
+ join_array[level] = [] unless join_array[level]
112
+
113
+ node.each do |relationship, relationship_details|
114
+ relationship_details[:resource_klasses].each do |related_resource_klass, resource_details|
115
+ join_array[level] << { relationship: relationship,
116
+ relationship_details: relationship_details,
117
+ related_resource_klass: related_resource_klass}
118
+ flatten_join_tree_by_depth(join_array, resource_details[:relationships], level+1)
119
+ end
120
+ end
121
+ join_array
122
+ end
123
+
124
+ def add_join_details(join_key, details, check_for_duplicate_alias = true)
125
+ fail "details already set" if @join_details.has_key?(join_key)
126
+ @join_details[join_key] = details
127
+
128
+ # Joins are being tracked as they are added to the built up relation. If the same table is added to a
129
+ # relation more than once subsequent versions will be assigned an alias. Depending on the order the joins
130
+ # are made the computed aliases may change. The order this library performs the joins was chosen
131
+ # to prevent this. However if the relation is reordered it should result in reusing on of the earlier
132
+ # aliases (in this case a plain table name). The following check will catch this an raise an exception.
133
+ # An exception is appropriate because not using the correct alias could leak data due to filters and
134
+ # applied permissions being performed on the wrong data.
135
+ if check_for_duplicate_alias && @collected_aliases.include?(details[:alias])
136
+ fail "alias '#{details[:alias]}' has already been added. Possible relation reordering"
137
+ end
138
+
139
+ @collected_aliases << details[:alias]
140
+ end
141
+
142
+ def perform_joins(records, options)
143
+ join_array = flatten_join_tree_by_depth
144
+
145
+ join_array.each do |level_joins|
146
+ level_joins.each do |join_details|
147
+ relationship = join_details[:relationship]
148
+ relationship_details = join_details[:relationship_details]
149
+ related_resource_klass = join_details[:related_resource_klass]
150
+ join_type = relationship_details[:join_type]
151
+
152
+ join_options = {
153
+ relationship: relationship,
154
+ relationship_details: relationship_details,
155
+ related_resource_klass: related_resource_klass,
156
+ }
157
+
158
+ if relationship == :root
159
+ unless source_relationship
160
+ add_join_details('', {alias: resource_klass._table_name, join_type: :root, join_options: join_options})
161
+ end
162
+ next
163
+ end
164
+
165
+ records, join_node = self.class.get_join_arel_node(records, options) {|records, options|
166
+ related_resource_klass.join_relationship(
167
+ records: records,
168
+ resource_type: related_resource_klass._type,
169
+ join_type: join_type,
170
+ relationship: relationship,
171
+ options: options)
172
+ }
173
+
174
+ details = {alias: self.class.alias_from_arel_node(join_node), join_type: join_type, join_options: join_options}
175
+
176
+ if relationship == source_relationship
177
+ if relationship.polymorphic? && relationship.belongs_to?
178
+ add_join_details("##{related_resource_klass._type}", details)
179
+ else
180
+ add_join_details('', details)
181
+ end
182
+ end
183
+
184
+ # We're adding the source alias with two keys. We only want the check for duplicate aliases once.
185
+ # See the note in `add_join_details`.
186
+ check_for_duplicate_alias = !(relationship == source_relationship)
187
+ add_join_details(PathSegment::Relationship.new(relationship: relationship, resource_klass: related_resource_klass), details, check_for_duplicate_alias)
188
+ end
189
+ end
190
+ records
191
+ end
192
+
193
+ def add_join(path, default_type = :inner, default_polymorphic_join_type = :left)
194
+ if source_relationship
195
+ if source_relationship.polymorphic?
196
+ # Polymorphic paths will come it with the resource_type as the first segment (for example `#documents.comments`)
197
+ # We just need to prepend the relationship portion the
198
+ sourced_path = "#{source_relationship.name}#{path}"
199
+ else
200
+ sourced_path = "#{source_relationship.name}.#{path}"
201
+ end
202
+ else
203
+ sourced_path = path
204
+ end
205
+
206
+ join_manager, _field = parse_path_to_tree(sourced_path, resource_klass, default_type, default_polymorphic_join_type)
207
+
208
+ @resource_join_tree[:root].deep_merge!(join_manager) { |key, val, other_val|
209
+ if key == :join_type
210
+ if val == other_val
211
+ val
212
+ else
213
+ :inner
214
+ end
215
+ end
216
+ }
217
+ end
218
+
219
+ def process_path_to_tree(path_segments, resource_klass, default_join_type, default_polymorphic_join_type)
220
+ node = {
221
+ resource_klasses: {
222
+ resource_klass => {
223
+ relationships: {}
224
+ }
225
+ }
226
+ }
227
+
228
+ segment = path_segments.shift
229
+
230
+ if segment.is_a?(PathSegment::Relationship)
231
+ node[:resource_klasses][resource_klass][:relationships][segment.relationship] ||= {}
232
+
233
+ # join polymorphic as left joins
234
+ node[:resource_klasses][resource_klass][:relationships][segment.relationship][:join_type] ||=
235
+ segment.relationship.polymorphic? ? default_polymorphic_join_type : default_join_type
236
+
237
+ segment.relationship.resource_types.each do |related_resource_type|
238
+ related_resource_klass = resource_klass.resource_klass_for(related_resource_type)
239
+
240
+ # If the resource type was specified in the path segment we want to only process the next segments for
241
+ # that resource type, otherwise process for all
242
+ process_all_types = !segment.path_specified_resource_klass?
243
+
244
+ if process_all_types || related_resource_klass == segment.resource_klass
245
+ related_resource_tree = process_path_to_tree(path_segments.dup, related_resource_klass, default_join_type, default_polymorphic_join_type)
246
+ node[:resource_klasses][resource_klass][:relationships][segment.relationship].deep_merge!(related_resource_tree)
247
+ end
248
+ end
249
+ end
250
+ node
251
+ end
252
+
253
+ def parse_path_to_tree(path_string, resource_klass, default_join_type = :inner, default_polymorphic_join_type = :left)
254
+ path = JSONAPI::Path.new(resource_klass: resource_klass, path_string: path_string)
255
+
256
+ field = path.segments[-1]
257
+ return process_path_to_tree(path.segments, resource_klass, default_join_type, default_polymorphic_join_type), field
258
+ end
259
+
260
+ def add_source_relationship(source_relationship)
261
+ @source_relationship = source_relationship
262
+
263
+ if @source_relationship
264
+ resource_klasses = {}
265
+ source_relationship.resource_types.each do |related_resource_type|
266
+ related_resource_klass = resource_klass.resource_klass_for(related_resource_type)
267
+ resource_klasses[related_resource_klass] = {relationships: {}}
268
+ end
269
+
270
+ join_type = source_relationship.polymorphic? ? :left : :inner
271
+
272
+ @resource_join_tree[:root][:resource_klasses][resource_klass][:relationships][@source_relationship] = {
273
+ source: true, resource_klasses: resource_klasses, join_type: join_type
274
+ }
275
+ end
276
+ end
277
+
278
+ def add_filters(filters)
279
+ return if filters.blank?
280
+ filters.each_key do |filter|
281
+ # Do not add joins for filters with an apply callable. This can be overridden by setting perform_joins to true
282
+ next if resource_klass._allowed_filters[filter].try(:[], :apply) &&
283
+ !resource_klass._allowed_filters[filter].try(:[], :perform_joins)
284
+
285
+ add_join(filter, :left)
286
+ end
287
+ end
288
+
289
+ def add_sort_criteria(sort_criteria)
290
+ return if sort_criteria.blank?
291
+
292
+ sort_criteria.each do |sort|
293
+ add_join(sort[:field], :left)
294
+ end
295
+ end
296
+
297
+ def add_relationships(relationships)
298
+ return if relationships.blank?
299
+ relationships.each do |relationship|
300
+ add_join(relationship, :left)
301
+ end
302
+ end
303
+ end
304
+ end
305
+ end