activerecord-filter 6.0.0.4 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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