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 +4 -4
- data/README.md +77 -30
- data/lib/active_record/filter.rb +146 -84
- data/lib/active_record/filter/version.rb +1 -1
- metadata +16 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a22c93c0080f2e92aaf6b427e89eb6ac73210bc40304941e7e17373b70c987c
|
4
|
+
data.tar.gz: 7180d339c3fed586489cd65a4de59e34d5edaf7860584c12683f144134844470
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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(:
|
18
|
-
|
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(:
|
21
|
-
|
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(:
|
24
|
-
# => "...WHERE properties.id
|
27
|
+
Property.filter(id: [5, 10, 15]).to_sql
|
28
|
+
# => "... WHERE properties.id IN (5, 10, 15) ..."
|
25
29
|
|
26
|
-
Property.filter(:
|
27
|
-
# => "...WHERE properties.id
|
30
|
+
Property.filter(id: {in: [5, 10, 15]}).to_sql
|
31
|
+
# => "... WHERE properties.id IN (5, 10, 15) ..."
|
28
32
|
|
29
|
-
Property.filter(:
|
30
|
-
# => "...WHERE properties.id
|
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(:
|
33
|
-
|
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(:
|
36
|
-
|
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(:
|
39
|
-
|
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(:
|
42
|
-
|
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(:
|
83
|
+
Property.filter(tags: 'Skyscraper').to_sql
|
49
84
|
# => "...WHERE properties.tags = '{'Skyscraper'}'..."
|
50
85
|
|
51
|
-
Property.filter(:
|
52
|
-
# => "...WHERE
|
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(:
|
55
|
-
# => "...WHERE
|
95
|
+
Property.filter(tags: {excludes: ['Skyscraper', 'Brick']}).to_sql
|
96
|
+
# => "...WHERE NOT (accounts.tags @> '{"Skyscraper", "Brick"}')..."
|
56
97
|
|
57
|
-
Property.filter(:
|
58
|
-
# => "...WHERE accounts.tags
|
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(:
|
81
|
-
# => "...INNER JOIN properties ON properties.id = photos.property_id
|
82
|
-
# => "
|
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
|
+
```
|
data/lib/active_record/filter.rb
CHANGED
@@ -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
|
-
|
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.
|
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.
|
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
|
-
|
138
|
-
|
139
|
-
|
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.
|
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.
|
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.
|
314
|
+
return table.arel_table[relation.foreign_key].not_eq(nil)
|
278
315
|
elsif value == false || value == 'false' || value.nil?
|
279
|
-
return table.
|
316
|
+
return table.arel_table[relation.foreign_key].eq(nil)
|
280
317
|
end
|
281
318
|
end
|
282
|
-
|
283
|
-
|
284
|
-
|
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
|
-
|
291
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
|
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.
|
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:
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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:
|
212
|
+
version: 1.3.1
|
213
213
|
requirements: []
|
214
|
-
rubygems_version: 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
|