jsonapi-resources 0.10.7 → 0.11.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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 -17
  28. data/lib/jsonapi/path.rb +2 -0
  29. data/lib/jsonapi/path_segment.rb +4 -2
  30. data/lib/jsonapi/processor.rb +100 -153
  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: d30a2ea26f6bb9f907e0710a7e9d95a9682bfa64ab0fd7ae1e5b350c07f52924
4
- data.tar.gz: fd883de5a6506f4aaad03fcf3ec61ec311c0e57f0cf33ea8c3d8b082c47b2671
3
+ metadata.gz: 3b0c18d478ae14531738238de633c47aace50c6f7a2c3ff25e9ce09033e802ce
4
+ data.tar.gz: 8365f223895635d28c12ecf334193ed4f3a30368a3a4d674fc5e65366f3d317f
5
5
  SHA512:
6
- metadata.gz: cc53b7071fb93b2f2f5c8ff2f02b43572a56d5da8a58835cff73953100db55c0ea9dbc2a1d26b7b06ae854d8e588276619d7631a4dc509e7c6332b9ced5c6245
7
- data.tar.gz: d9f77c39f058f3978b20ccba5d2beea4744bbda9cb1b19933ed9a3a235a5265a9fecb945a2fd3646f0135cb97059896be3ac2fa19023a94fd555480ac8483f95
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