activerecord-filter 8.0.0 → 9.0.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 +4 -4
- data/README.md +37 -4
- data/lib/active_record/filter/alias_tracker_extension.rb +2 -0
- data/lib/active_record/filter/filter_clause_factory.rb +4 -2
- data/lib/active_record/filter/predicate_builder_extension.rb +29 -9
- data/lib/active_record/filter/query_methods_extension.rb +3 -5
- data/lib/active_record/filter/relation_extension.rb +10 -1
- data/lib/active_record/filter/spawn_methods_extension.rb +2 -0
- data/lib/active_record/filter/unkown_filter_error.rb +2 -0
- data/lib/active_record/filter/version.rb +3 -1
- data/lib/active_record/filter.rb +2 -0
- metadata +31 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a42ca6ff3bdbd4318c13518684d31f6bdf3b7a98aa02e877e2d8147c83d2efef
|
|
4
|
+
data.tar.gz: 5b595328b4129bced6fa43a87c6f93398d6cb35942bb0aa693874b49a062de91
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e1500189bf99dae963016fff43cb78cda9aa42fe771907fd7842562d67ea704b4609822e85a3f559f96776c3f084a54e790cfdd4bc1026731da44989b7a5109
|
|
7
|
+
data.tar.gz: 6f9aeae084bf0b7efde2b69dc20a1ac15b8312bbedd800820f5b84fa7e733147357a09fdcf8272e2c1a263077f2fff93a40c5aebd60b9ef3ce2adbd90537385a
|
data/README.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# ActiveRecord::Filter
|
|
2
2
|
|
|
3
|
-
`ActiveRecord::Filter` provides
|
|
3
|
+
`ActiveRecord::Filter` provides an easy way to accept user input and filter a query by that input.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Ruby >= 3.3
|
|
8
|
+
- ActiveRecord >= 8.0
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
7
11
|
|
|
8
12
|
- Add `gem 'activerecord-filter', require: 'active_record/filter'`
|
|
9
13
|
- Run `bundle install`
|
|
@@ -124,10 +128,39 @@ Property.filter("metadata.key": { eq: 'value' }).to_sql
|
|
|
124
128
|
# => "...WHERE "properties"."metadata" #> '{key}' = 'value'..."
|
|
125
129
|
```
|
|
126
130
|
|
|
127
|
-
It can also
|
|
131
|
+
It can also filter across associations. Any association (`belongs_to`,
|
|
132
|
+
`has_many`, `has_one`, `has_and_belongs_to_many`, `has_many :through`) can be
|
|
133
|
+
nested, and the join is added for you:
|
|
128
134
|
|
|
129
135
|
```ruby
|
|
130
136
|
Photo.filter(property: {name: 'Empire State'}).to_sql
|
|
131
137
|
# => "... LEFT OUTER JOIN properties ON properties.id = photos.property_id ...
|
|
132
138
|
# => "... WHERE properties.name = 'Empire State'"
|
|
133
139
|
```
|
|
140
|
+
|
|
141
|
+
Pass `true`/`false` against an association to filter on its presence:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
Account.filter(photos: true).to_sql # accounts that have photos
|
|
145
|
+
Account.filter(photos: false).to_sql # accounts with no photos
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
For a polymorphic association, scope it to a type with `as:`:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
View.filter(subject: {as: 'Property', name: 'Name'}).to_sql
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Combining conditions with AND / OR
|
|
155
|
+
----------------------------------
|
|
156
|
+
|
|
157
|
+
Pass an array to group conditions. Use the strings `'AND'` / `'OR'` as
|
|
158
|
+
separators, and nest arrays for precedence:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
Property.filter([{id: 10}, 'OR', {name: 'name'}]).to_sql
|
|
162
|
+
# => "... WHERE ((properties.id = 10) OR (properties.name = 'name'))"
|
|
163
|
+
|
|
164
|
+
Property.filter([{id: 10}, 'AND', [{id: 10}, 'OR', {name: 'name'}]]).to_sql
|
|
165
|
+
# => "... WHERE properties.id = 10 AND ((properties.id = 10) OR (properties.name = 'name'))"
|
|
166
|
+
```
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class ActiveRecord::Filter::FilterClauseFactory
|
|
2
4
|
|
|
3
5
|
def initialize(model, predicate_builder)
|
|
4
|
-
@
|
|
6
|
+
@klass = model
|
|
5
7
|
@predicate_builder = predicate_builder
|
|
6
8
|
end
|
|
7
9
|
|
|
@@ -17,6 +19,6 @@ class ActiveRecord::Filter::FilterClauseFactory
|
|
|
17
19
|
|
|
18
20
|
protected
|
|
19
21
|
|
|
20
|
-
attr_reader :
|
|
22
|
+
attr_reader :klass, :predicate_builder
|
|
21
23
|
|
|
22
24
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "active_support/concern"
|
|
2
4
|
|
|
3
5
|
module ActiveRecord::Filter::PredicateBuilderExtension
|
|
@@ -26,6 +28,15 @@ module ActiveRecord::Filter::PredicateBuilderExtension
|
|
|
26
28
|
relations << j
|
|
27
29
|
end
|
|
28
30
|
end
|
|
31
|
+
elsif js.is_a?(Proc)
|
|
32
|
+
resolved = js.call(value)
|
|
33
|
+
Array(resolved).compact.each do |j|
|
|
34
|
+
if j.is_a?(String)
|
|
35
|
+
custom << j
|
|
36
|
+
else
|
|
37
|
+
relations << j
|
|
38
|
+
end
|
|
39
|
+
end
|
|
29
40
|
elsif js
|
|
30
41
|
if js.is_a?(String)
|
|
31
42
|
custom << js
|
|
@@ -53,17 +64,17 @@ module ActiveRecord::Filter::PredicateBuilderExtension
|
|
|
53
64
|
]
|
|
54
65
|
else
|
|
55
66
|
{
|
|
56
|
-
|
|
67
|
+
reflection.name => build_filter_joins(reflection.klass, value, [], custom)
|
|
57
68
|
}
|
|
58
69
|
end
|
|
59
70
|
elsif value.is_a?(Array)
|
|
60
71
|
value.each do |v|
|
|
61
72
|
relations << {
|
|
62
|
-
|
|
73
|
+
reflection.name => build_filter_joins(reflection.klass, v, [], custom)
|
|
63
74
|
}
|
|
64
75
|
end
|
|
65
76
|
elsif value != true && value != false && value != 'true' && value != 'false' && !value.nil?
|
|
66
|
-
relations <<
|
|
77
|
+
relations << reflection.name
|
|
67
78
|
end
|
|
68
79
|
elsif !klass.columns_hash.has_key?(key.to_s) && key.to_s.end_with?('_ids') && reflection = klass._reflections[key.to_s.gsub(/_ids$/, 's').to_sym]
|
|
69
80
|
relations << reflection.name
|
|
@@ -297,18 +308,18 @@ module ActiveRecord::Filter::PredicateBuilderExtension
|
|
|
297
308
|
value = value.dup
|
|
298
309
|
klass = value.delete(:as).safe_constantize
|
|
299
310
|
|
|
300
|
-
builder =
|
|
311
|
+
builder = new_predicate_builder_extension(
|
|
301
312
|
klass,
|
|
302
313
|
alias_tracker.aliased_table_for_relation(relation_trail + ["#{klass.table_name}_as_#{relation.name}"], klass.arel_table) { klass.arel_table.name },
|
|
303
314
|
relation
|
|
304
|
-
)
|
|
315
|
+
)
|
|
305
316
|
builder.build_from_filter_hash(value, relation_trail + ["#{klass.table_name}_as_#{relation.name}"], alias_tracker)
|
|
306
317
|
else
|
|
307
|
-
builder =
|
|
318
|
+
builder = new_predicate_builder_extension(
|
|
308
319
|
relation.klass,
|
|
309
320
|
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) },
|
|
310
321
|
relation
|
|
311
|
-
)
|
|
322
|
+
)
|
|
312
323
|
builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
|
|
313
324
|
end
|
|
314
325
|
|
|
@@ -316,12 +327,21 @@ module ActiveRecord::Filter::PredicateBuilderExtension
|
|
|
316
327
|
|
|
317
328
|
def expand_filter_for_join_table(relation, value, relation_trail, alias_tracker)
|
|
318
329
|
relation = relation.active_record._reflections[relation.active_record._reflections[relation.name].send(:delegate_reflection).options[:through]]
|
|
319
|
-
builder =
|
|
330
|
+
builder = new_predicate_builder_extension(
|
|
320
331
|
relation.klass,
|
|
321
332
|
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) },
|
|
322
333
|
relation
|
|
323
|
-
)
|
|
334
|
+
)
|
|
324
335
|
builder.build_from_filter_hash(value, relation_trail + [relation.name], alias_tracker)
|
|
325
336
|
end
|
|
326
337
|
|
|
338
|
+
if ActiveRecord.version >= "8.1"
|
|
339
|
+
def new_predicate_builder_extension(a,b,c)
|
|
340
|
+
self.class.new(ActiveRecord::TableMetadata.new(a, b))
|
|
341
|
+
end
|
|
342
|
+
else
|
|
343
|
+
def new_predicate_builder_extension(a,b,c)
|
|
344
|
+
self.class.new(ActiveRecord::TableMetadata.new(a, b, c))
|
|
345
|
+
end
|
|
346
|
+
end
|
|
327
347
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord::Filter::QueryMethodsExtension
|
|
2
4
|
private
|
|
3
5
|
def build_join_buckets
|
|
@@ -8,11 +10,7 @@ private
|
|
|
8
10
|
left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
|
|
9
11
|
if left_join.is_a?(ActiveRecord::QueryMethods::CTEJoin)
|
|
10
12
|
buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
|
|
11
|
-
|
|
12
|
-
# Changed a line https://github.com/rails/rails/blob/ae2983a75ca658d84afa414dea8eaf1cca87aa23/activerecord/lib/active_record/relation/query_methods.rb#L1769
|
|
13
|
-
# that was probably a bug beforehand but allowed nodes to be joined
|
|
14
|
-
# which I think was and still is supported?
|
|
15
|
-
elsif left_join.is_a?(Arel::Nodes::OuterJoin)
|
|
13
|
+
elsif left_join.is_a?(Arel::Nodes::Join)
|
|
16
14
|
buckets[:join_node] << left_join
|
|
17
15
|
else
|
|
18
16
|
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord::Filter::RelationExtension
|
|
2
4
|
|
|
3
5
|
def initialize(*, **)
|
|
@@ -49,7 +51,14 @@ module ActiveRecord::Filter::RelationExtension
|
|
|
49
51
|
@filter_clause_factory ||= ActiveRecord::Filter::FilterClauseFactory.new(klass, predicate_builder)
|
|
50
52
|
end
|
|
51
53
|
|
|
52
|
-
if ActiveRecord.version >= "
|
|
54
|
+
if ActiveRecord.version >= "8.1"
|
|
55
|
+
def build_arel(aliases = nil)
|
|
56
|
+
arel = super
|
|
57
|
+
my_alias_tracker = ActiveRecord::Associations::AliasTracker.create(model.connection_pool, table.name, [])
|
|
58
|
+
build_filters(arel, my_alias_tracker)
|
|
59
|
+
arel
|
|
60
|
+
end
|
|
61
|
+
elsif ActiveRecord.version >= "7.2"
|
|
53
62
|
def build_arel(connection, aliases = nil)
|
|
54
63
|
arel = super
|
|
55
64
|
my_alias_tracker = ActiveRecord::Associations::AliasTracker.create(model.connection_pool, table.name, [])
|
data/lib/active_record/filter.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-filter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 9.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jon Bracy
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activerecord
|
|
@@ -16,28 +15,28 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
18
|
+
version: 8.0.0
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
25
|
+
version: 8.0.0
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: arel-extensions
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
30
29
|
requirements:
|
|
31
30
|
- - ">="
|
|
32
31
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
32
|
+
version: 8.0.0
|
|
34
33
|
type: :runtime
|
|
35
34
|
prerelease: false
|
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
36
|
requirements:
|
|
38
37
|
- - ">="
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
39
|
+
version: 8.0.0
|
|
41
40
|
- !ruby/object:Gem::Dependency
|
|
42
41
|
name: pg
|
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -58,14 +57,14 @@ dependencies:
|
|
|
58
57
|
requirements:
|
|
59
58
|
- - ">="
|
|
60
59
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
60
|
+
version: 8.0.0
|
|
62
61
|
type: :development
|
|
63
62
|
prerelease: false
|
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
64
|
requirements:
|
|
66
65
|
- - ">="
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
|
-
version:
|
|
67
|
+
version: 8.0.0
|
|
69
68
|
- !ruby/object:Gem::Dependency
|
|
70
69
|
name: bundler
|
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -122,6 +121,20 @@ dependencies:
|
|
|
122
121
|
- - ">="
|
|
123
122
|
- !ruby/object:Gem::Version
|
|
124
123
|
version: '0'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: benchmark
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0'
|
|
125
138
|
- !ruby/object:Gem::Dependency
|
|
126
139
|
name: simplecov
|
|
127
140
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -142,14 +155,14 @@ dependencies:
|
|
|
142
155
|
requirements:
|
|
143
156
|
- - ">="
|
|
144
157
|
- !ruby/object:Gem::Version
|
|
145
|
-
version:
|
|
158
|
+
version: 8.0.0
|
|
146
159
|
type: :development
|
|
147
160
|
prerelease: false
|
|
148
161
|
version_requirements: !ruby/object:Gem::Requirement
|
|
149
162
|
requirements:
|
|
150
163
|
- - ">="
|
|
151
164
|
- !ruby/object:Gem::Version
|
|
152
|
-
version:
|
|
165
|
+
version: 8.0.0
|
|
153
166
|
- !ruby/object:Gem::Dependency
|
|
154
167
|
name: faker
|
|
155
168
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -214,8 +227,11 @@ files:
|
|
|
214
227
|
homepage: https://github.com/malomalo/activerecord-filter
|
|
215
228
|
licenses:
|
|
216
229
|
- MIT
|
|
217
|
-
metadata:
|
|
218
|
-
|
|
230
|
+
metadata:
|
|
231
|
+
homepage_uri: https://github.com/malomalo/activerecord-filter
|
|
232
|
+
source_code_uri: https://github.com/malomalo/activerecord-filter
|
|
233
|
+
changelog_uri: https://github.com/malomalo/activerecord-filter/blob/master/CHANGELOG.md
|
|
234
|
+
rubygems_mfa_required: 'true'
|
|
219
235
|
rdoc_options:
|
|
220
236
|
- "--main"
|
|
221
237
|
- README.md
|
|
@@ -225,15 +241,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
225
241
|
requirements:
|
|
226
242
|
- - ">="
|
|
227
243
|
- !ruby/object:Gem::Version
|
|
228
|
-
version: '
|
|
244
|
+
version: '3.3'
|
|
229
245
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
246
|
requirements:
|
|
231
247
|
- - ">="
|
|
232
248
|
- !ruby/object:Gem::Version
|
|
233
249
|
version: '0'
|
|
234
250
|
requirements: []
|
|
235
|
-
rubygems_version:
|
|
236
|
-
signing_key:
|
|
251
|
+
rubygems_version: 4.0.11
|
|
237
252
|
specification_version: 4
|
|
238
253
|
summary: A safe way to accept user parameters and query against your ActiveRecord
|
|
239
254
|
Models
|