activerecord-filter 6.0.0.3 → 6.1.0.rc1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d5c6eebcaf0ce67328ce60b589d3db745379c3e1f9e167a72b6d529c12dd242
4
- data.tar.gz: 12e0734ef2c953958cdbef7725e71273d7f6173abeb9e2155c81eb5096e93f87
3
+ metadata.gz: 0a22c93c0080f2e92aaf6b427e89eb6ac73210bc40304941e7e17373b70c987c
4
+ data.tar.gz: 7180d339c3fed586489cd65a4de59e34d5edaf7860584c12683f144134844470
5
5
  SHA512:
6
- metadata.gz: 4faa92743587408661292c04b9100929519354d194a583a0ba9fadb51e208d895ee0d79c2d188563479ce8dc51892d6678f039644ae41cccdafc53eae28c226d
7
- data.tar.gz: 15acb3d28304602f7c24b2ac966f37cd809a31bd36ba4d0fed0f2a8c8d3cdfebe16eb2ea536309d05a458146b31cde62b8fd9e6c6ee1ff7b82e924c44351ff41
6
+ metadata.gz: 4950a8448ea79f7a1ea671889e1353b28657722b3eab8ba202d6c80b02f0d16a2ee9ca4c7893fd24b9b3a436e7a95c7c7a87568345ae6b565a22e8791cec5b51
7
+ data.tar.gz: a061d579c68764f56729d2afba8a979f67804d285b6e1ac895e6ce20722d382e4a636b583b9598607f57340535baae2b369b4d4fcb67e28e8b0599de46c5adbd
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  Installtion
6
6
  -----------
7
7
 
8
- - Add `gem 'activerecord-filter', require: 'active_record/filter'
8
+ - Add `gem 'activerecord-filter', require: 'active_record/filter'`
9
9
  - Run `bundle install`
10
10
 
11
11
  Examples
@@ -14,48 +14,89 @@ Examples
14
14
  Normal columns:
15
15
 
16
16
  ```ruby
17
- Property.filter(:id => 5).to_sql
18
- # => "...WHERE properties.id = 5"
17
+ Property.filter(id: 5).to_sql
18
+ Property.filter(id: {eq: 5}).to_sql
19
+ Property.filter(id: {equal_to: 5}).to_sql
20
+ # => "... WHERE properties.id = 5 ..."
19
21
 
20
- Property.filter(:id => [5, 10, 15]).to_sql
21
- # => "...WHERE properties.id IN (5, 10, 15)"
22
+ Property.filter(id: {not: 5}).to_sql
23
+ Property.filter(id: {neq: 5}).to_sql
24
+ Property.filter(id: {not_equal: 5}).to_sql
25
+ # => "... WHERE properties.id != 5 ..."
22
26
 
23
- Property.filter(:id => {:gt => 5}).to_sql
24
- # => "...WHERE properties.id > 5"
27
+ Property.filter(id: [5, 10, 15]).to_sql
28
+ # => "... WHERE properties.id IN (5, 10, 15) ..."
25
29
 
26
- Property.filter(:id => {:gte => 5}).to_sql
27
- # => "...WHERE properties.id >= 5"
30
+ Property.filter(id: {in: [5, 10, 15]}).to_sql
31
+ # => "... WHERE properties.id IN (5, 10, 15) ..."
28
32
 
29
- Property.filter(:id => {:lt => 5}).to_sql
30
- # => "...WHERE properties.id < 5"
33
+ Property.filter(id: {not_in: [5, 10, 15]}).to_sql
34
+ # => "... WHERE properties.id NOT IN (5, 10, 15) ..."
31
35
 
32
- Property.filter(:id => {:lte => 5}).to_sql
33
- # => "...WHERE properties.id <= 5"
36
+ Property.filter(id: {gt: 5}).to_sql
37
+ Property.filter(id: {greater_than: 5}).to_sql
38
+ # => "... WHERE properties.id > 5 ..."
34
39
 
35
- Property.filter(:address_id => nil).to_sql
36
- # => "...WHERE properties.address_id IS NULL..."
40
+ Property.filter(id: {gte: 5}).to_sql
41
+ Property.filter(id: {gteq: 5}).to_sql
42
+ Property.filter(id: {greater_than_or_equal_to: 5}).to_sql
43
+ # => "... WHERE properties.id >= 5 ..."
37
44
 
38
- Property.filter(:address_id => false).to_sql
39
- # => "...WHERE properties.address_id IS NULL..."
45
+ Property.filter(id: {lt: 5}).to_sql
46
+ Property.filter(id: {less_than: 5}).to_sql
47
+ # => "... WHERE properties.id < 5 ..."
40
48
 
41
- Property.filter(:address_id => true).to_sql
42
- # => "...WHERE properties.address_id IS NOT NULL..."
49
+ Property.filter(id: {lte: 5}).to_sql
50
+ Property.filter(id: {lteq: 5}).to_sql
51
+ Property.filter(id: {less_than_or_equal_to: 5}).to_sql
52
+ # => "... WHERE properties.id <= 5 ..."
53
+
54
+ Property.filter(address_id: nil).to_sql
55
+ # => "... WHERE properties.address_id IS NULL ..."
56
+
57
+ Property.filter(address_id: false).to_sql
58
+ # => "... WHERE properties.address_id IS NULL ..."
59
+
60
+ Property.filter(boolean_column: false).to_sql
61
+ # => "... WHERE properties.boolean_column = FALSE ..."
62
+
63
+ Property.filter(address_id: true).to_sql
64
+ # => "... WHERE properties.address_id IS NOT NULL ..."
65
+
66
+ Property.filter(boolean_column: true).to_sql
67
+ # => "... WHERE properties.boolean_column = TRUE ..."
68
+ ```
69
+
70
+ String columns:
71
+
72
+ ```ruby
73
+ Property.filter(name: {like: 'nam%'}).to_sql
74
+ # => "... WHERE properties.name LIKE 'nam%' ..."
75
+
76
+ Property.filter(name: {ts_match: 'name'}).to_sql
77
+ # => "... WHERE to_tsvector("properties"."name") @@ to_tsquery('name') ..."
43
78
  ```
44
79
 
45
80
  It can also work with array columns:
46
81
 
47
82
  ```ruby
48
- Property.filter(:tags => 'Skyscraper').to_sql
83
+ Property.filter(tags: 'Skyscraper').to_sql
49
84
  # => "...WHERE properties.tags = '{'Skyscraper'}'..."
50
85
 
51
- Property.filter(:tags => ['Skyscraper', 'Brick']).to_sql
52
- # => "...WHERE (properties.tags = '{"Skyscraper", "Brick"}')..."
86
+ Property.filter(tags: ['Skyscraper', 'Brick']).to_sql
87
+ # => "...WHERE properties.tags = '{"Skyscraper", "Brick"}'..."
88
+
89
+ Property.filter(tags: {overlaps: ['Skyscraper', 'Brick']}).to_sql
90
+ # => "...WHERE properties.tags && '{"Skyscraper", "Brick"}'..."
91
+
92
+ Property.filter(tags: {contains: ['Skyscraper', 'Brick']}).to_sql
93
+ # => "...WHERE accounts.tags @> '{"Skyscraper", "Brick"}'..."
53
94
 
54
- Property.filter(:tags => {overlaps: ['Skyscraper', 'Brick']}).to_sql
55
- # => "...WHERE properties.tags && '{"Skyscraper", "Brick"}')..."
95
+ Property.filter(tags: {excludes: ['Skyscraper', 'Brick']}).to_sql
96
+ # => "...WHERE NOT (accounts.tags @> '{"Skyscraper", "Brick"}')..."
56
97
 
57
- Property.filter(:tags => {contains: ['Skyscraper', 'Brick']}).to_sql
58
- # => "...WHERE accounts.tags @> '{"Skyscraper", "Brick"}')..."
98
+ Property.filter(tags: {contained_by: ['Skyscraper', 'Brick']}).to_sql
99
+ # => "...WHERE accounts.tags <@ '{"Skyscraper", "Brick"}'..."
59
100
  ```
60
101
 
61
102
  And JSON columns:
@@ -70,6 +111,12 @@ Property.filter(metadata: { contains: { key: 'value' } }).to_sql
70
111
  Property.filter(metadata: { has_key: 'key' }).to_sql
71
112
  # => "...WHERE "properties"."metadata" ? 'key'..."
72
113
 
114
+ Property.filter(metadata: { has_keys: ['key1', 'key2'] }).to_sql
115
+ # => "...WHERE "properties"."metadata" ?& array['key1', 'key2']..."
116
+
117
+ Property.filter(metadata: { has_any_key: ['key1', 'key2'] }).to_sql
118
+ # => "...WHERE "properties"."metadata" ?| array['key1', 'key2']..."
119
+
73
120
  Property.filter("metadata.key": { eq: 'value' }).to_sql
74
121
  # => "...WHERE "properties"."metadata" #> '{key}' = 'value'..."
75
122
  ```
@@ -77,7 +124,7 @@ Property.filter("metadata.key": { eq: 'value' }).to_sql
77
124
  It can also sort on relations:
78
125
 
79
126
  ```ruby
80
- Photo.filter(:property => {name: 'Empire State'}).to_sql
81
- # => "...INNER JOIN properties ON properties.id = photos.property_id
82
- # => " WHERE properties.name = 'Empire State'"
83
- ```
127
+ Photo.filter(property: {name: 'Empire State'}).to_sql
128
+ # => "... INNER JOIN properties ON properties.id = photos.property_id ...
129
+ # => "... WHERE properties.name = 'Empire State'"
130
+ ```
@@ -4,26 +4,40 @@ require 'arel/extensions'
4
4
  class ActiveRecord::UnkownFilterError < NoMethodError
5
5
  end
6
6
 
7
+ class ActiveRecord::Associations::AliasTracker
8
+
9
+ def initialize(connection, aliases)
10
+ @aliases = aliases
11
+ @connection = connection
12
+ @relation_trail = {}
13
+ end
14
+
15
+ def aliased_table_for_relation(trail, arel_table, &block)
16
+ @relation_trail[trail] ||= aliased_table_for(arel_table, &block)
17
+ end
18
+
19
+ end
20
+
7
21
  module ActiveRecord::Filter
8
22
 
9
23
  delegate :filter, :filter_for, to: :all
10
-
24
+
11
25
  def inherited(subclass)
12
26
  super
13
27
  subclass.instance_variable_set('@filters', HashWithIndifferentAccess.new)
14
28
  end
15
-
29
+
16
30
  def filters
17
31
  @filters
18
32
  end
19
-
33
+
20
34
  def filter_on(name, dependent_joins=nil, &block)
21
35
  @filters[name.to_s] = {
22
36
  joins: dependent_joins,
23
37
  block: block
24
38
  }
25
39
  end
26
-
40
+
27
41
  end
28
42
 
29
43
  module ActiveRecord
@@ -33,7 +47,7 @@ module ActiveRecord
33
47
  custom = []
34
48
  [build_filter_joins(klass, filters, [], custom), custom]
35
49
  end
36
-
50
+
37
51
  def self.build_filter_joins(klass, filters, relations=[], custom=[])
38
52
  if filters.is_a?(Array)
39
53
  filters.each { |f| build_filter_joins(klass, f, relations, custom) }.compact
@@ -41,7 +55,7 @@ module ActiveRecord
41
55
  filters.each do |key, value|
42
56
  if klass.filters.has_key?(key.to_sym)
43
57
  js = klass.filters.dig(key.to_sym, :joins)
44
-
58
+
45
59
  if js.is_a?(Array)
46
60
  js.each do |j|
47
61
  if j.is_a?(String)
@@ -59,13 +73,37 @@ module ActiveRecord
59
73
  end
60
74
  elsif reflection = klass._reflections[key.to_s]
61
75
  if value.is_a?(Hash)
62
- relations << {
63
- key => build_filter_joins(reflection.klass, value, [], custom)
64
- }
76
+ relations << if reflection.polymorphic?
77
+ value = value.dup
78
+ join_klass = value.delete(:as).safe_constantize
79
+ right_table = join_klass.arel_table
80
+ left_table = reflection.active_record.arel_table
81
+
82
+ on = right_table[join_klass.primary_key].
83
+ eq(left_table[reflection.foreign_key]).
84
+ and(left_table[reflection.foreign_type].eq(join_klass.name))
85
+
86
+ cross_boundry_joins = join_klass.left_outer_joins(ActiveRecord::PredicateBuilder.filter_joins(join_klass, value).flatten).send(:build_joins, [])
87
+
88
+ [
89
+ left_table.join(right_table, Arel::Nodes::OuterJoin).on(on).join_sources,
90
+ cross_boundry_joins
91
+ ]
92
+ else
93
+ {
94
+ key => build_filter_joins(reflection.klass, value, [], custom)
95
+ }
96
+ end
97
+ elsif value.is_a?(Array)
98
+ value.each do |v|
99
+ relations << {
100
+ key => build_filter_joins(reflection.klass, v, [], custom)
101
+ }
102
+ end
65
103
  elsif value != true && value != false && value != 'true' && value != 'false' && !value.nil?
66
104
  relations << key
67
105
  end
68
- elsif !klass.columns_hash.has_key?(key.to_s) && key.to_s.ends_with?('_ids') && reflection = klass._reflections[key.to_s.gsub(/_ids$/, 's')]
106
+ elsif !klass.columns_hash.has_key?(key.to_s) && key.to_s.end_with?('_ids') && reflection = klass._reflections[key.to_s.gsub(/_ids$/, 's')]
69
107
  relations << reflection.name
70
108
  elsif reflection = klass.reflect_on_all_associations(:has_and_belongs_to_many).find {|r| r.join_table == key.to_s && value.keys.first.to_s == r.association_foreign_key.to_s }
71
109
  reflection = klass._reflections[klass._reflections[reflection.name.to_s].send(:delegate_reflection).options[:through].to_s]
@@ -73,17 +111,17 @@ module ActiveRecord
73
111
  end
74
112
  end
75
113
  end
76
-
114
+
77
115
  relations
78
116
  end
79
-
80
- def build_from_filter_hash(attributes)
117
+
118
+ def build_from_filter_hash(attributes, relation_trail, alias_tracker)
81
119
  if attributes.is_a?(Array)
82
- node = build_from_filter_hash(attributes.shift)
120
+ node = build_from_filter_hash(attributes.shift, relation_trail, alias_tracker)
83
121
 
84
122
  n = attributes.shift(2)
85
123
  while !n.empty?
86
- n[1] = build_from_filter_hash(n[1])
124
+ n[1] = build_from_filter_hash(n[1], relation_trail, alias_tracker)
87
125
  if n[0] == 'AND'
88
126
  if node.is_a?(Arel::Nodes::And)
89
127
  node.children.push(n[1])
@@ -92,39 +130,46 @@ module ActiveRecord
92
130
  end
93
131
  elsif n[0] == 'OR'
94
132
  node = Arel::Nodes::Grouping.new(node).or(Arel::Nodes::Grouping.new(n[1]))
133
+ elsif !n[0].is_a?(String)
134
+ n[0] = build_from_filter_hash(n[0], relation_trail, alias_tracker)
135
+ if node.is_a?(Arel::Nodes::And)
136
+ node.children.push(n[0])
137
+ else
138
+ node = node.and(n[0])
139
+ end
95
140
  else
96
141
  raise 'lll'
97
142
  end
98
143
  n = attributes.shift(2)
99
144
  end
100
-
145
+
101
146
  node
102
147
  elsif attributes.is_a?(Hash)
103
- expand_from_filter_hash(attributes)
148
+ expand_from_filter_hash(attributes, relation_trail, alias_tracker)
104
149
  else
105
- expand_from_filter_hash({id: attributes})
150
+ expand_from_filter_hash({id: attributes}, relation_trail, alias_tracker)
106
151
  end
107
152
  end
108
-
109
- def expand_from_filter_hash(attributes)
153
+
154
+ def expand_from_filter_hash(attributes, relation_trail, alias_tracker)
110
155
  klass = table.send(:klass)
111
-
156
+
112
157
  children = attributes.flat_map do |key, value|
113
158
  if custom_filter = klass.filters[key]
114
- self.instance_exec(klass, table, key, value, &custom_filter[:block])
159
+ self.instance_exec(klass, table, key, value, relation_trail, alias_tracker, &custom_filter[:block])
115
160
  elsif column = klass.columns_hash[key.to_s] || klass.columns_hash[key.to_s.split('.').first]
116
- expand_filter_for_column(key, column, value)
161
+ expand_filter_for_column(key, column, value, relation_trail)
117
162
  elsif relation = klass.reflect_on_association(key)
118
- expand_filter_for_relationship(relation, value)
119
- elsif key.to_s.ends_with?('_ids') && relation = klass.reflect_on_association(key.to_s.gsub(/_ids$/, 's'))
120
- expand_filter_for_relationship(relation, {id: value})
163
+ expand_filter_for_relationship(relation, value, relation_trail, alias_tracker)
164
+ elsif key.to_s.end_with?('_ids') && relation = klass.reflect_on_association(key.to_s.gsub(/_ids$/, 's'))
165
+ expand_filter_for_relationship(relation, {id: value}, relation_trail, alias_tracker)
121
166
  elsif relation = klass.reflect_on_all_associations(:has_and_belongs_to_many).find {|r| r.join_table == key.to_s && value.keys.first.to_s == r.association_foreign_key.to_s }
122
- expand_filter_for_join_table(relation, value)
167
+ expand_filter_for_join_table(relation, value, relation_trail, alias_tracker)
123
168
  else
124
169
  raise ActiveRecord::UnkownFilterError.new("Unkown filter \"#{key}\" for #{klass}.")
125
170
  end
126
171
  end
127
-
172
+
128
173
  children.compact!
129
174
  if children.size > 1
130
175
  Arel::Nodes::And.new(children)
@@ -132,20 +177,11 @@ module ActiveRecord
132
177
  children.first
133
178
  end
134
179
  end
135
-
136
- def expand_filter_for_column(key, column, value)
137
- # Not sure why
138
- # activerecord/lib/active_record/table_metadata.rb#arel_attribute
139
- # doesn't work here, something's not working with a
140
- # Arel::Nodes::TableAlias, would like to go back to it one day
141
- attribute = if klass = table.send(:klass)
142
- if Arel::Nodes::TableAlias === table.send(:arel_table)
143
- klass.arel_attribute(column.name, table.send(:arel_table).left)
144
- else
145
- klass.arel_attribute(column.name, table.send(:arel_table))
146
- end
147
- else
148
- table.send(:arel_table)[column.name]
180
+
181
+ def expand_filter_for_column(key, column, value, relation_trail)
182
+ attribute = table.arel_table[column.name]
183
+ relation_trail.each do |rt|
184
+ attribute = Arel::Attributes::Relation.new(attribute, rt)
149
185
  end
150
186
 
151
187
  if column.type == :json || column.type == :jsonb
@@ -153,7 +189,7 @@ module ActiveRecord
153
189
  names.shift
154
190
  attribute = attribute.dig(names)
155
191
  end
156
-
192
+
157
193
  if value.is_a?(Hash)
158
194
  nodes = value.map do |subkey, subvalue|
159
195
  expand_filter_for_arel_attribute(column, attribute, subkey, subvalue)
@@ -173,19 +209,19 @@ module ActiveRecord
173
209
  else
174
210
  raise ActiveRecord::UnkownFilterError.new("Unkown type for #{column}. (type #{value.class})")
175
211
  end
176
-
212
+
177
213
  end
178
-
214
+
179
215
  def expand_filter_for_arel_attribute(column, attribute, key, value)
180
216
  case key.to_sym
181
217
  when :contains
182
- attribute.contains(column.array ? Array(value) : value)
218
+ attribute.contains(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
183
219
  when :contained_by
184
- attribute.contained_by(column.array ? Array(value) : value)
220
+ attribute.contained_by(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
185
221
  when :equal_to, :eq
186
222
  attribute.eq(value)
187
223
  when :excludes
188
- attribute.excludes(Array(value))
224
+ attribute.excludes(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
189
225
  when :greater_than, :gt
190
226
  attribute.gt(value)
191
227
  when :greater_than_or_equal_to, :gteq, :gte
@@ -206,7 +242,7 @@ module ActiveRecord
206
242
  # elsif # EWKT
207
243
  # elsif # WKT
208
244
  # end
209
-
245
+
210
246
  # TODO us above if to determin if SRID sent
211
247
  geometry_value = if value.is_a?(Hash)
212
248
  Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromGeoJSON', [Arel::Nodes.build_quoted(JSON.generate(subvalue))]), 4326])
@@ -228,7 +264,7 @@ module ActiveRecord
228
264
  when :not_in
229
265
  attribute.not_in(value)
230
266
  when :overlaps
231
- attribute.overlaps(value)
267
+ attribute.overlaps(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
232
268
  when :not_overlaps
233
269
  attribute.not_overlaps(value)
234
270
  when :ts_match
@@ -250,45 +286,67 @@ module ActiveRecord
250
286
  raise "Not Supported value for within: #{value.inspect}"
251
287
  end
252
288
  else
253
- raise "Not Supported: #{key.to_sym}"
289
+ raise "Not Supported: #{key.to_sym} on column \"#{column.name}\" of type #{column.type}"
254
290
  end
255
291
  end
256
-
257
- def expand_filter_for_relationship(relation, value)
292
+
293
+ def expand_filter_for_relationship(relation, value, relation_trail, alias_tracker)
258
294
  case relation.macro
259
295
  when :has_many
260
296
  if value == true || value == 'true'
261
297
  counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
262
298
  if relation.active_record.column_names.include?(counter_cache_column_name.to_s)
263
- return table.arel_attribute(counter_cache_column_name.to_sym).gt(0)
299
+ return table.arel_table[counter_cache_column_name.to_sym].gt(0)
264
300
  else
265
301
  raise "Not Supported: #{relation.name}"
266
302
  end
267
303
  elsif value == false || value == 'false'
268
304
  counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
269
305
  if relation.active_record.column_names.include?(counter_cache_column_name.to_s)
270
- return table.arel_attribute(counter_cache_column_name.to_sym).eq(0)
306
+ return table.arel_table[counter_cache_column_name.to_sym].eq(0)
271
307
  else
272
308
  raise "Not Supported: #{relation.name}"
273
309
  end
274
310
  end
311
+
275
312
  when :belongs_to
276
313
  if value == true || value == 'true'
277
- return table.arel_attribute(relation.foreign_key).not_eq(nil)
314
+ return table.arel_table[relation.foreign_key].not_eq(nil)
278
315
  elsif value == false || value == 'false' || value.nil?
279
- return table.arel_attribute(relation.foreign_key).eq(nil)
316
+ return table.arel_table[relation.foreign_key].eq(nil)
280
317
  end
281
318
  end
282
-
283
- builder = associated_predicate_builder(relation.name.to_sym)
284
- builder.build_from_filter_hash(value)
319
+
320
+ if relation.polymorphic?
321
+ value = value.dup
322
+ klass = value.delete(:as).safe_constantize
323
+
324
+ builder = self.class.new(TableMetadata.new(
325
+ klass,
326
+ alias_tracker.aliased_table_for_relation(relation_trail + ["#{klass.table_name}_as_#{relation.name}"], klass.arel_table) { klass.arel_table.name },
327
+ relation
328
+ ))
329
+ builder.build_from_filter_hash(value, relation_trail + ["#{klass.table_name}_as_#{relation.name}"], alias_tracker)
330
+ else
331
+ builder = self.class.new(TableMetadata.new(
332
+ relation.klass,
333
+ alias_tracker.aliased_table_for_relation(relation_trail + [relation.name], relation.klass.arel_table) { relation.alias_candidate(table.arel_table.name || relation.klass.arel_table) },
334
+ relation
335
+ ))
336
+ builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
337
+ end
338
+
285
339
  end
286
-
287
- def expand_filter_for_join_table(relation, value)
288
- relation = relation.active_record._reflections[relation.active_record._reflections[relation.name.to_s].send(:delegate_reflection).options[:through].to_s]
289
340
 
290
- builder = associated_predicate_builder(relation.name.to_sym)
291
- builder.build_from_filter_hash(value)
341
+
342
+ def expand_filter_for_join_table(relation, value, relation_trail, alias_tracker)
343
+ relation = relation.active_record._reflections[relation.active_record._reflections[relation.name.to_s].send(:delegate_reflection).options[:through].to_s]
344
+ builder = self.class.new(TableMetadata.new(
345
+ relation.klass,
346
+ alias_tracker.aliased_table_for_relation(relation_trail + [relation.name], relation.klass.arel_table) { relation.alias_candidate(table.arel_table.name || relation.klass.arel_table) },
347
+ relation
348
+ ))
349
+ builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
292
350
  end
293
351
 
294
352
  end
@@ -303,18 +361,13 @@ module ActiveRecord
303
361
  @predicate_builder = predicate_builder
304
362
  end
305
363
 
306
- def build(filters)
364
+ def build(filters, alias_tracker)
307
365
  if filters.is_a?(Hash) || filters.is_a?(Array)
308
- # attributes = predicate_builder.resolve_column_aliases(filters)
309
- # attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
310
- # attributes.stringify_keys!
311
- #
312
- # attributes, binds = predicate_builder.create_binds(attributes)
313
- parts = [predicate_builder.build_from_filter_hash(filters)]
366
+ parts = [predicate_builder.build_from_filter_hash(filters, [], alias_tracker)]
314
367
  else
315
368
  raise ArgumentError, "Unsupported argument type: #{filters.inspect} (#{filters.class})"
316
369
  end
317
-
370
+
318
371
  WhereClause.new(parts)
319
372
  end
320
373
 
@@ -332,12 +385,12 @@ class ActiveRecord::Relation
332
385
  @filters = []
333
386
  super
334
387
  end
335
-
388
+
336
389
  def initialize_copy(other)
337
390
  @filters = @filters.deep_dup
338
391
  super
339
392
  end
340
-
393
+
341
394
  def clean_filters(value)
342
395
  if value.class.name == 'ActionController::Parameters'.freeze
343
396
  value.to_unsafe_h
@@ -350,34 +403,43 @@ class ActiveRecord::Relation
350
403
 
351
404
  def filter(filters)
352
405
  filters = clean_filters(filters)
353
-
406
+
354
407
  if filters.nil? || filters.empty?
355
408
  self
356
409
  else
357
410
  spawn.filter!(filters)
358
411
  end
359
412
  end
360
-
413
+
361
414
  def filter!(filters)
362
415
  js = ActiveRecord::PredicateBuilder.filter_joins(klass, filters)
363
- js.each { |j| joins!(j) if j.present? }
416
+ js.flatten.each do |j|
417
+ if j.is_a?(String)
418
+ joins!(j)
419
+ elsif j.is_a?(Arel::Nodes::Join)
420
+ joins!(j)
421
+ elsif j.present?
422
+ left_outer_joins!(j)
423
+ end
424
+ end
364
425
  @filters << filters
365
426
  self
366
427
  end
367
-
428
+
368
429
  def filter_clause_factory
369
430
  @filter_clause_factory ||= FilterClauseFactory.new(klass, predicate_builder)
370
431
  end
371
-
432
+
372
433
  def build_arel(aliases)
373
434
  arel = super
374
- build_filters(arel)
435
+ my_alias_tracker = ActiveRecord::Associations::AliasTracker.create(connection, table.name, [])
436
+ build_filters(arel, my_alias_tracker)
375
437
  arel
376
438
  end
377
-
378
- def build_filters(manager)
439
+
440
+ def build_filters(manager, alias_tracker)
379
441
  @filters.each do |filters|
380
- manager.where(filter_clause_factory.build(filters).ast)
442
+ manager.where(filter_clause_factory.build(filters, alias_tracker).ast)
381
443
  end
382
444
  end
383
445
 
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Filter
3
- VERSION = '6.0.0.3'
3
+ VERSION = '6.1.0.rc1'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-filter
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0.3
4
+ version: 6.1.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Bracy
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-16 00:00:00.000000000 Z
11
+ date: 2020-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 6.0.0
19
+ version: 6.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 6.0.0
26
+ version: 6.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: arel-extensions
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 6.0.0
33
+ version: 6.1.0.rc2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 6.0.0
40
+ version: 6.1.0.rc2
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pg
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 6.0.0
61
+ version: 6.1.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 6.0.0
68
+ version: 6.1.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -142,14 +142,14 @@ dependencies:
142
142
  requirements:
143
143
  - - ">="
144
144
  - !ruby/object:Gem::Version
145
- version: 6.0.0
145
+ version: 6.1.0
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
- version: 6.0.0
152
+ version: 6.1.0
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: faker
155
155
  requirement: !ruby/object:Gem::Requirement
@@ -194,7 +194,7 @@ homepage: https://github.com/malomalo/activerecord-filter
194
194
  licenses:
195
195
  - MIT
196
196
  metadata: {}
197
- post_install_message:
197
+ post_install_message:
198
198
  rdoc_options:
199
199
  - "--main"
200
200
  - README.md
@@ -207,12 +207,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
207
207
  version: '0'
208
208
  required_rubygems_version: !ruby/object:Gem::Requirement
209
209
  requirements:
210
- - - ">="
210
+ - - ">"
211
211
  - !ruby/object:Gem::Version
212
- version: '0'
212
+ version: 1.3.1
213
213
  requirements: []
214
- rubygems_version: 3.0.3
215
- signing_key:
214
+ rubygems_version: 3.1.4
215
+ signing_key:
216
216
  specification_version: 4
217
217
  summary: A safe way to accept user parameters and query against your ActiveRecord
218
218
  Models