activerecord-filter 6.0.0.6 → 6.1.0.1

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: c34aa8bbd31df158c37ad5a80c46c31dff156bfb2a6e0bbb7e699d8919eb9aa1
4
- data.tar.gz: eb154ad06ccc2b7b24c4b7ebea90bd4708b95a92db2b7d8fd92f74e2ca71c4c6
3
+ metadata.gz: 15ee3eb761b9e63875afecd98becf6b5f6b0482937610ee0a9303cfe7d8d0b75
4
+ data.tar.gz: 85b0b14cee45da61b44326810765078ad7a838af806168950a0ee50d48b5a2c6
5
5
  SHA512:
6
- metadata.gz: 8d715a7a707abc56c16512e2aee2fe4fd4995e82466c3651d2caf14d38bb72f0201e0445c72614c4d295d05ccc6adf66666a1287644b33612d14aa472de804b9
7
- data.tar.gz: 2f4535f34a7e762c4ce68abb18530e7f0b9ca9b7f8bd6a16e6389626f806806bc89a486104c17d61cb7361dc106437a203d0fe598ce8ace34136c23048282b66
6
+ metadata.gz: b6f47b5bc745550bb48c1cd6aa85642e20511b5235befe0acd123909e0faa422c2b53662a26e8db4da8ae44029b0d0ddf44fd5179e48f23ee25e3463634bc164
7
+ data.tar.gz: 2240aadf3eea6b58f1b2d16e7e0cb785e7250938538533bf33d565871a15d845f0a1ea496cdec8b520bc575c66f330953dd300eabc1d75be405c029468e19124
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # ActiveRecord::filter
1
+ # ActiveRecord::Filter
2
2
 
3
- `ActiveRecord::filter` provides and easy way to accept user input and filter a query by the input.
3
+ `ActiveRecord::Filter` provides and easy way to accept user input and filter a query by the input.
4
4
 
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,92 @@ 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: {ilike: 'nam%'}).to_sql
77
+ # => "... WHERE properties.name ILIKE 'nam%' ..."
78
+
79
+ Property.filter(name: {ts_match: 'name'}).to_sql
80
+ # => "... WHERE to_tsvector("properties"."name") @@ to_tsquery('name') ..."
43
81
  ```
44
82
 
45
83
  It can also work with array columns:
46
84
 
47
85
  ```ruby
48
- Property.filter(:tags => 'Skyscraper').to_sql
86
+ Property.filter(tags: 'Skyscraper').to_sql
49
87
  # => "...WHERE properties.tags = '{'Skyscraper'}'..."
50
88
 
51
- Property.filter(:tags => ['Skyscraper', 'Brick']).to_sql
52
- # => "...WHERE (properties.tags = '{"Skyscraper", "Brick"}')..."
89
+ Property.filter(tags: ['Skyscraper', 'Brick']).to_sql
90
+ # => "...WHERE properties.tags = '{"Skyscraper", "Brick"}'..."
91
+
92
+ Property.filter(tags: {overlaps: ['Skyscraper', 'Brick']}).to_sql
93
+ # => "...WHERE properties.tags && '{"Skyscraper", "Brick"}'..."
53
94
 
54
- Property.filter(:tags => {overlaps: ['Skyscraper', 'Brick']}).to_sql
55
- # => "...WHERE properties.tags && '{"Skyscraper", "Brick"}')..."
95
+ Property.filter(tags: {contains: ['Skyscraper', 'Brick']}).to_sql
96
+ # => "...WHERE 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: {excludes: ['Skyscraper', 'Brick']}).to_sql
99
+ # => "...WHERE NOT (accounts.tags @> '{"Skyscraper", "Brick"}')..."
100
+
101
+ Property.filter(tags: {contained_by: ['Skyscraper', 'Brick']}).to_sql
102
+ # => "...WHERE accounts.tags <@ '{"Skyscraper", "Brick"}'..."
59
103
  ```
60
104
 
61
105
  And JSON columns:
@@ -70,6 +114,12 @@ Property.filter(metadata: { contains: { key: 'value' } }).to_sql
70
114
  Property.filter(metadata: { has_key: 'key' }).to_sql
71
115
  # => "...WHERE "properties"."metadata" ? 'key'..."
72
116
 
117
+ Property.filter(metadata: { has_keys: ['key1', 'key2'] }).to_sql
118
+ # => "...WHERE "properties"."metadata" ?& array['key1', 'key2']..."
119
+
120
+ Property.filter(metadata: { has_any_key: ['key1', 'key2'] }).to_sql
121
+ # => "...WHERE "properties"."metadata" ?| array['key1', 'key2']..."
122
+
73
123
  Property.filter("metadata.key": { eq: 'value' }).to_sql
74
124
  # => "...WHERE "properties"."metadata" #> '{key}' = 'value'..."
75
125
  ```
@@ -77,7 +127,7 @@ Property.filter("metadata.key": { eq: 'value' }).to_sql
77
127
  It can also sort on relations:
78
128
 
79
129
  ```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'"
130
+ Photo.filter(property: {name: 'Empire State'}).to_sql
131
+ # => "... LEFT OUTER JOIN properties ON properties.id = photos.property_id ...
132
+ # => "... WHERE properties.name = 'Empire State'"
83
133
  ```
@@ -4,6 +4,20 @@ 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
@@ -60,16 +74,21 @@ module ActiveRecord
60
74
  elsif reflection = klass._reflections[key.to_s]
61
75
  if value.is_a?(Hash)
62
76
  relations << if reflection.polymorphic?
63
- join_klass = value[:as].safe_constantize
64
-
65
- right_table = join_klass.arel_table.alias("#{join_klass.table_name}_as_#{reflection.name}")
77
+ value = value.dup
78
+ join_klass = value.delete(:as).safe_constantize
79
+ right_table = join_klass.arel_table
66
80
  left_table = reflection.active_record.arel_table
67
81
 
68
82
  on = right_table[join_klass.primary_key].
69
83
  eq(left_table[reflection.foreign_key]).
70
84
  and(left_table[reflection.foreign_type].eq(join_klass.name))
71
85
 
72
- left_table.join(right_table, Arel::Nodes::OuterJoin).on(on).join_sources
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
+ ]
73
92
  else
74
93
  {
75
94
  key => build_filter_joins(reflection.klass, value, [], custom)
@@ -84,7 +103,7 @@ module ActiveRecord
84
103
  elsif value != true && value != false && value != 'true' && value != 'false' && !value.nil?
85
104
  relations << key
86
105
  end
87
- 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')]
88
107
  relations << reflection.name
89
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 }
90
109
  reflection = klass._reflections[klass._reflections[reflection.name.to_s].send(:delegate_reflection).options[:through].to_s]
@@ -142,7 +161,7 @@ module ActiveRecord
142
161
  expand_filter_for_column(key, column, value, relation_trail)
143
162
  elsif relation = klass.reflect_on_association(key)
144
163
  expand_filter_for_relationship(relation, value, relation_trail, alias_tracker)
145
- 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'))
146
165
  expand_filter_for_relationship(relation, {id: value}, relation_trail, alias_tracker)
147
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 }
148
167
  expand_filter_for_join_table(relation, value, relation_trail, alias_tracker)
@@ -160,7 +179,7 @@ module ActiveRecord
160
179
  end
161
180
 
162
181
  def expand_filter_for_column(key, column, value, relation_trail)
163
- attribute = table.arel_attribute(column.name)
182
+ attribute = table.arel_table[column.name]
164
183
  relation_trail.each do |rt|
165
184
  attribute = Arel::Attributes::Relation.new(attribute, rt)
166
185
  end
@@ -196,13 +215,13 @@ module ActiveRecord
196
215
  def expand_filter_for_arel_attribute(column, attribute, key, value)
197
216
  case key.to_sym
198
217
  when :contains
199
- attribute.contains(column.array ? Array(value) : value)
218
+ attribute.contains(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
200
219
  when :contained_by
201
- attribute.contained_by(column.array ? Array(value) : value)
220
+ attribute.contained_by(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
202
221
  when :equal_to, :eq
203
222
  attribute.eq(value)
204
223
  when :excludes
205
- attribute.excludes(Array(value))
224
+ attribute.excludes(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
206
225
  when :greater_than, :gt
207
226
  attribute.gt(value)
208
227
  when :greater_than_or_equal_to, :gteq, :gte
@@ -238,14 +257,16 @@ module ActiveRecord
238
257
  attribute.lt(value)
239
258
  when :less_than_or_equal_to, :lteq, :lte
240
259
  attribute.lteq(value)
241
- when :like, :ilike
242
- attribute.matches(value)
260
+ when :like
261
+ attribute.matches(value, nil, true)
262
+ when :ilike
263
+ attribute.matches(value, nil, false)
243
264
  when :not, :not_equal, :neq
244
265
  attribute.not_eq(value)
245
266
  when :not_in
246
267
  attribute.not_in(value)
247
268
  when :overlaps
248
- attribute.overlaps(value)
269
+ attribute.overlaps(Arel::Nodes::Casted.new(column.array ? Array(value) : value, attribute))
249
270
  when :not_overlaps
250
271
  attribute.not_overlaps(value)
251
272
  when :ts_match
@@ -277,14 +298,14 @@ module ActiveRecord
277
298
  if value == true || value == 'true'
278
299
  counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
279
300
  if relation.active_record.column_names.include?(counter_cache_column_name.to_s)
280
- return table.arel_attribute(counter_cache_column_name.to_sym).gt(0)
301
+ return table.arel_table[counter_cache_column_name.to_sym].gt(0)
281
302
  else
282
303
  raise "Not Supported: #{relation.name}"
283
304
  end
284
305
  elsif value == false || value == 'false'
285
306
  counter_cache_column_name = relation.counter_cache_column || "#{relation.plural_name}_count"
286
307
  if relation.active_record.column_names.include?(counter_cache_column_name.to_s)
287
- return table.arel_attribute(counter_cache_column_name.to_sym).eq(0)
308
+ return table.arel_table[counter_cache_column_name.to_sym].eq(0)
288
309
  else
289
310
  raise "Not Supported: #{relation.name}"
290
311
  end
@@ -292,33 +313,31 @@ module ActiveRecord
292
313
 
293
314
  when :belongs_to
294
315
  if value == true || value == 'true'
295
- return table.arel_attribute(relation.foreign_key).not_eq(nil)
316
+ return table.arel_table[relation.foreign_key].not_eq(nil)
296
317
  elsif value == false || value == 'false' || value.nil?
297
- return table.arel_attribute(relation.foreign_key).eq(nil)
318
+ return table.arel_table[relation.foreign_key].eq(nil)
298
319
  end
299
320
  end
300
321
 
301
- builder = if relation.polymorphic?
322
+ if relation.polymorphic?
302
323
  value = value.dup
303
324
  klass = value.delete(:as).safe_constantize
304
325
 
305
- self.class.new(TableMetadata.new(
306
- klass,
307
- Arel::Table.new("#{klass.table_name}_as_#{relation.name}", type_caster: klass.type_caster),
326
+ builder = self.class.new(TableMetadata.new(
327
+ klass,
328
+ alias_tracker.aliased_table_for_relation(relation_trail + ["#{klass.table_name}_as_#{relation.name}"], klass.arel_table) { klass.arel_table.name },
308
329
  relation
309
330
  ))
331
+ builder.build_from_filter_hash(value, relation_trail + ["#{klass.table_name}_as_#{relation.name}"], alias_tracker)
310
332
  else
311
- self.class.new(TableMetadata.new(
333
+ builder = self.class.new(TableMetadata.new(
312
334
  relation.klass,
313
- alias_tracker.aliased_table_for(
314
- relation.table_name,
315
- relation.alias_candidate(table.send(:arel_table).name),
316
- relation.klass.type_caster
317
- ),
335
+ 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) },
318
336
  relation
319
337
  ))
338
+ builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
320
339
  end
321
- builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
340
+
322
341
  end
323
342
 
324
343
 
@@ -326,11 +345,7 @@ module ActiveRecord
326
345
  relation = relation.active_record._reflections[relation.active_record._reflections[relation.name.to_s].send(:delegate_reflection).options[:through].to_s]
327
346
  builder = self.class.new(TableMetadata.new(
328
347
  relation.klass,
329
- alias_tracker.aliased_table_for(
330
- relation.table_name,
331
- relation.alias_candidate(table.send(:arel_table).name),
332
- relation.klass.type_caster
333
- ),
348
+ 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
349
  relation
335
350
  ))
336
351
  builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
@@ -424,7 +439,7 @@ class ActiveRecord::Relation
424
439
  arel
425
440
  end
426
441
 
427
- def build_filters(manager, aliases)
442
+ def build_filters(manager, alias_tracker)
428
443
  @filters.each do |filters|
429
444
  manager.where(filter_clause_factory.build(filters, alias_tracker).ast)
430
445
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Filter
3
- VERSION = '6.0.0.6'
3
+ VERSION = '6.1.0.1'
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.6
4
+ version: 6.1.0.1
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: 2020-04-27 00:00:00.000000000 Z
11
+ date: 2021-07-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
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
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.6
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