activerecord-filter 6.0.0.4 → 6.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86c7f7cf36ec41fc4dae21d1460151fcf2a32a9813f13a8b0acec56ebdccfbdd
4
- data.tar.gz: 9993953d4a401d46e1bfd60102162794fd6f13d496d2fcd3352534fa0601ef90
3
+ metadata.gz: 29fb0cf2e61a150f0ea6aaa4877961ccd58008daeb7af2bb9766f726f9b37e46
4
+ data.tar.gz: 7492a6050879102efac043637217972388f607af7c62a3b4a176b04d7695ea9b
5
5
  SHA512:
6
- metadata.gz: 69ad9b34f9b0511a0e34d76ef819b147a5705b9d0731fdca7fdcd3b876a90d1685321386033354653c39d30d3fb96283a4e4443e5a330bc405736e3342c24e06
7
- data.tar.gz: f466d0b394b040109ba460a24a1811c8f4550bdeb9ca17904a4906ee1c2f011e59bf38481e074c29e08b4f6af4ea81360352bebb65fa45e49a285377d798f291
6
+ metadata.gz: 9c2f00f35c2eeff0a93866024fbacaf5a24f14843d82ccf81e5b0eefb36918a3892424c00a3b5f01d8f42f176e63331b3ca90fb54be388900517572be44857d9
7
+ data.tar.gz: e3bd52216a2cbd41e9769cf7b813a5b69af63f03c45a7616e7cb0eea444711f746fd8a1bcb7b377b5b8c604463c011263f802473db072990d7a1559590c06e0d
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,10 +111,10 @@ module ActiveRecord
73
111
  end
74
112
  end
75
113
  end
76
-
114
+
77
115
  relations
78
116
  end
79
-
117
+
80
118
  def build_from_filter_hash(attributes, relation_trail, alias_tracker)
81
119
  if attributes.is_a?(Array)
82
120
  node = build_from_filter_hash(attributes.shift, relation_trail, alias_tracker)
@@ -92,12 +130,19 @@ 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
148
  expand_from_filter_hash(attributes, relation_trail, alias_tracker)
@@ -105,10 +150,10 @@ module ActiveRecord
105
150
  expand_from_filter_hash({id: attributes}, relation_trail, alias_tracker)
106
151
  end
107
152
  end
108
-
153
+
109
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
159
  self.instance_exec(klass, table, key, value, relation_trail, alias_tracker, &custom_filter[:block])
@@ -116,7 +161,7 @@ module ActiveRecord
116
161
  expand_filter_for_column(key, column, value, relation_trail)
117
162
  elsif relation = klass.reflect_on_association(key)
118
163
  expand_filter_for_relationship(relation, value, relation_trail, alias_tracker)
119
- elsif key.to_s.ends_with?('_ids') && relation = klass.reflect_on_association(key.to_s.gsub(/_ids$/, 's'))
164
+ elsif key.to_s.end_with?('_ids') && relation = klass.reflect_on_association(key.to_s.gsub(/_ids$/, 's'))
120
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
167
  expand_filter_for_join_table(relation, value, relation_trail, alias_tracker)
@@ -124,7 +169,7 @@ module ActiveRecord
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,19 +177,19 @@ module ActiveRecord
132
177
  children.first
133
178
  end
134
179
  end
135
-
180
+
136
181
  def expand_filter_for_column(key, column, value, relation_trail)
137
- attribute = table.arel_attribute(column.name)
182
+ attribute = table.arel_table[column.name]
138
183
  relation_trail.each do |rt|
139
184
  attribute = Arel::Attributes::Relation.new(attribute, rt)
140
185
  end
141
-
186
+
142
187
  if column.type == :json || column.type == :jsonb
143
188
  names = key.to_s.split('.')
144
189
  names.shift
145
190
  attribute = attribute.dig(names)
146
191
  end
147
-
192
+
148
193
  if value.is_a?(Hash)
149
194
  nodes = value.map do |subkey, subvalue|
150
195
  expand_filter_for_arel_attribute(column, attribute, subkey, subvalue)
@@ -164,19 +209,19 @@ module ActiveRecord
164
209
  else
165
210
  raise ActiveRecord::UnkownFilterError.new("Unkown type for #{column}. (type #{value.class})")
166
211
  end
167
-
212
+
168
213
  end
169
-
214
+
170
215
  def expand_filter_for_arel_attribute(column, attribute, key, value)
171
216
  case key.to_sym
172
217
  when :contains
173
- attribute.contains(column.array ? Array(value) : value)
218
+ attribute.contains(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
174
219
  when :contained_by
175
- attribute.contained_by(column.array ? Array(value) : value)
220
+ attribute.contained_by(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
176
221
  when :equal_to, :eq
177
222
  attribute.eq(value)
178
223
  when :excludes
179
- attribute.excludes(Array(value))
224
+ attribute.excludes(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
180
225
  when :greater_than, :gt
181
226
  attribute.gt(value)
182
227
  when :greater_than_or_equal_to, :gteq, :gte
@@ -197,7 +242,7 @@ module ActiveRecord
197
242
  # elsif # EWKT
198
243
  # elsif # WKT
199
244
  # end
200
-
245
+
201
246
  # TODO us above if to determin if SRID sent
202
247
  geometry_value = if value.is_a?(Hash)
203
248
  Arel::Nodes::NamedFunction.new('ST_SetSRID', [Arel::Nodes::NamedFunction.new('ST_GeomFromGeoJSON', [Arel::Nodes.build_quoted(JSON.generate(subvalue))]), 4326])
@@ -219,7 +264,7 @@ module ActiveRecord
219
264
  when :not_in
220
265
  attribute.not_in(value)
221
266
  when :overlaps
222
- attribute.overlaps(value)
267
+ attribute.overlaps(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
223
268
  when :not_overlaps
224
269
  attribute.not_overlaps(value)
225
270
  when :ts_match
@@ -244,60 +289,61 @@ module ActiveRecord
244
289
  raise "Not Supported: #{key.to_sym} on column \"#{column.name}\" of type #{column.type}"
245
290
  end
246
291
  end
247
-
292
+
248
293
  def expand_filter_for_relationship(relation, value, relation_trail, alias_tracker)
249
294
  case relation.macro
250
295
  when :has_many
251
296
  if value == true || value == 'true'
252
297
  counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
253
298
  if relation.active_record.column_names.include?(counter_cache_column_name.to_s)
254
- 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)
255
300
  else
256
301
  raise "Not Supported: #{relation.name}"
257
302
  end
258
303
  elsif value == false || value == 'false'
259
304
  counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
260
305
  if relation.active_record.column_names.include?(counter_cache_column_name.to_s)
261
- 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)
262
307
  else
263
308
  raise "Not Supported: #{relation.name}"
264
309
  end
265
310
  end
311
+
266
312
  when :belongs_to
267
313
  if value == true || value == 'true'
268
- return table.arel_attribute(relation.foreign_key).not_eq(nil)
314
+ return table.arel_table[relation.foreign_key].not_eq(nil)
269
315
  elsif value == false || value == 'false' || value.nil?
270
- return table.arel_attribute(relation.foreign_key).eq(nil)
316
+ return table.arel_table[relation.foreign_key].eq(nil)
271
317
  end
272
318
  end
273
319
 
274
- builder = self.class.new(TableMetadata.new(
275
- relation.klass,
276
- alias_tracker.aliased_table_for(
277
- relation.table_name,
278
- relation.alias_candidate(table.send(:arel_table).name),
279
- relation.klass.type_caster
280
- ),
281
- relation
282
- ))
283
- builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
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
+
284
339
  end
285
-
286
-
340
+
341
+
287
342
  def expand_filter_for_join_table(relation, value, relation_trail, alias_tracker)
288
343
  relation = relation.active_record._reflections[relation.active_record._reflections[relation.name.to_s].send(:delegate_reflection).options[:through].to_s]
289
- STDOUT.puts [
290
- relation.table_name,
291
- relation.alias_candidate(table.send(:arel_table).name)
292
-
293
- ].inspect
294
344
  builder = self.class.new(TableMetadata.new(
295
345
  relation.klass,
296
- alias_tracker.aliased_table_for(
297
- relation.table_name,
298
- relation.alias_candidate(table.send(:arel_table).name),
299
- relation.klass.type_caster
300
- ),
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) },
301
347
  relation
302
348
  ))
303
349
  builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
@@ -321,7 +367,7 @@ module ActiveRecord
321
367
  else
322
368
  raise ArgumentError, "Unsupported argument type: #{filters.inspect} (#{filters.class})"
323
369
  end
324
-
370
+
325
371
  WhereClause.new(parts)
326
372
  end
327
373
 
@@ -339,12 +385,12 @@ class ActiveRecord::Relation
339
385
  @filters = []
340
386
  super
341
387
  end
342
-
388
+
343
389
  def initialize_copy(other)
344
390
  @filters = @filters.deep_dup
345
391
  super
346
392
  end
347
-
393
+
348
394
  def clean_filters(value)
349
395
  if value.class.name == 'ActionController::Parameters'.freeze
350
396
  value.to_unsafe_h
@@ -357,33 +403,41 @@ class ActiveRecord::Relation
357
403
 
358
404
  def filter(filters)
359
405
  filters = clean_filters(filters)
360
-
406
+
361
407
  if filters.nil? || filters.empty?
362
408
  self
363
409
  else
364
410
  spawn.filter!(filters)
365
411
  end
366
412
  end
367
-
413
+
368
414
  def filter!(filters)
369
415
  js = ActiveRecord::PredicateBuilder.filter_joins(klass, filters)
370
- 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
371
425
  @filters << filters
372
426
  self
373
427
  end
374
-
428
+
375
429
  def filter_clause_factory
376
430
  @filter_clause_factory ||= FilterClauseFactory.new(klass, predicate_builder)
377
431
  end
378
-
432
+
379
433
  def build_arel(aliases)
380
434
  arel = super
381
435
  my_alias_tracker = ActiveRecord::Associations::AliasTracker.create(connection, table.name, [])
382
436
  build_filters(arel, my_alias_tracker)
383
437
  arel
384
438
  end
385
-
386
- def build_filters(manager, aliases)
439
+
440
+ def build_filters(manager, alias_tracker)
387
441
  @filters.each do |filters|
388
442
  manager.where(filter_clause_factory.build(filters, alias_tracker).ast)
389
443
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Filter
3
- VERSION = '6.0.0.4'
3
+ VERSION = '6.1.0'
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.4
4
+ version: 6.1.0
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-09-18 00:00:00.000000000 Z
11
+ date: 2021-01-14 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.5
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.5
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
@@ -211,8 +211,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
211
  - !ruby/object:Gem::Version
212
212
  version: '0'
213
213
  requirements: []
214
- rubygems_version: 3.0.3
215
- signing_key:
214
+ rubygems_version: 3.2.3
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