activerecord_where_assoc 1.2.0 → 1.3.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/CHANGELOG.md +10 -0
- data/README.md +16 -25
- data/lib/active_record_where_assoc/active_record_compat.rb +23 -0
- data/lib/active_record_where_assoc/core_logic.rb +56 -6
- data/lib/active_record_where_assoc/relation_returning_methods.rb +37 -18
- data/lib/active_record_where_assoc/sql_returning_methods.rb +7 -2
- data/lib/active_record_where_assoc/version.rb +1 -1
- metadata +2 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c25ebdab306cdfa08879b9237211c464b023ab1445e2607ae18855b8546730bf
|
4
|
+
data.tar.gz: 76d893474ad646ab75258ab6856050193cd7bd17ccacef8a28fc7dc9e7c2de04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 110b91b46d60a9dbe82d31784e743e5f4d3cd4d67e3389593c97cd7461d17977e5ba941b265a7d30e04a8455d8c090f86c4ed54be87282d114ac6fa6fe712934
|
7
|
+
data.tar.gz: 14b76e774b2731063a10eaeb4654497c53521d44935172858697e7e9337e9e2032ba742c81e41bb48f925c305c53fd414d5e30817740766570ef60ee9a4853f0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 1.3.0 - 2025-03-04
|
4
|
+
|
5
|
+
* The arguments of `#where_assoc_count` can now be swapped when comparing to a number or a range. <br>
|
6
|
+
So `where_assoc_count(:posts, :>, 5)` is now valid for having more than 5 posts.
|
7
|
+
|
8
|
+
# 1.2.1 - 2024-12-05
|
9
|
+
|
10
|
+
* Optimize `has_one` handling for `#where_assoc_exists` with a `has_one` as last association + without any conditions or offset.
|
11
|
+
* Optimize `has_one` handling when the foreign_key has a unique index and there is no offset
|
12
|
+
|
3
13
|
# 1.2.0 - 2024-08-31
|
4
14
|
|
5
15
|
* Add support for composite primary keys in Rails 7.2
|
data/README.md
CHANGED
@@ -13,7 +13,7 @@ my_post.comments.where_assoc_not_exists(:author, is_admin: true).where(...)
|
|
13
13
|
Post.where_assoc_exists([:comments, :author], &:admins).where(...)
|
14
14
|
|
15
15
|
# Find my_user's posts that have at least 5 non-spam comments (not_spam is a scope on comments)
|
16
|
-
my_user.posts.where_assoc_count(
|
16
|
+
my_user.posts.where_assoc_count(:comments, :>=, 5) { |comments| comments.not_spam }.where(...)
|
17
17
|
```
|
18
18
|
|
19
19
|
These allow for powerful, chainable, clear and easy to reuse queries. (Great for scopes)
|
@@ -56,7 +56,7 @@ Or install it yourself with:
|
|
56
56
|
|
57
57
|
## Development state
|
58
58
|
|
59
|
-
This gem is feature complete and production ready
|
59
|
+
This gem is feature complete and production ready.<br>
|
60
60
|
Other than rare tweaks as new versions of Rails and Ruby are released, there shouldn't be much activity on this repository.
|
61
61
|
|
62
62
|
## Documentation
|
@@ -77,45 +77,36 @@ Otherwise, here is a short explanation of the main methods provided by this gem:
|
|
77
77
|
```ruby
|
78
78
|
where_assoc_exists(association_name, conditions, options, &block)
|
79
79
|
where_assoc_not_exists(association_name, conditions, options, &block)
|
80
|
-
where_assoc_count(
|
80
|
+
where_assoc_count(left_assoc_or_value, operator, right_assoc_or_value, conditions, options, &block)
|
81
81
|
```
|
82
82
|
|
83
|
-
* These methods add a condition (a `#where`) that checks if the association exists (or not)
|
84
|
-
* You can specify condition on the association, so you could check only for comments that are made by an admin.
|
85
|
-
* Each method returns a new relation, meaning you can chain `#where`, `#order`, `limit`, etc.
|
83
|
+
* These methods add a condition (a `#where`) that checks if the association exists (or not)
|
84
|
+
* You can specify condition on the association, so you could check only for comments that are made by an admin.
|
85
|
+
* Each method returns a new relation, meaning you can chain `#where`, `#order`, `limit`, etc.
|
86
86
|
* common arguments:
|
87
87
|
* association_name: the association we are doing the condition on.
|
88
88
|
* conditions: (optional) the condition to apply on the association. It can be anything that `#where` can receive, so: Hash, String and Array (string with binds).
|
89
89
|
* options: [available options](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/RelationReturningMethods#module-ActiveRecordWhereAssoc::RelationReturningMethods-label-Options) to alter some behaviors. (rarely necessary)
|
90
|
-
* block: adds more complex conditions by receiving a relation on the association. Can use `#where`, `#where_assoc_*`, scopes, and other scoping methods
|
91
|
-
Must return a relation
|
90
|
+
* block: adds more complex conditions by receiving a relation on the association. Can use `#where`, `#where_assoc_*`, scopes, and other scoping methods.<br>
|
91
|
+
Must return a relation.<br>
|
92
92
|
The block either:
|
93
93
|
* receives no argument, in which case `self` is set to the relation, so you can do `{ where(id: 123) }`
|
94
94
|
* receives arguments, in which case the block is called with the relation as first parameter.
|
95
95
|
|
96
|
-
The block should return the new relation to use or `nil` to do as if there were no blocks
|
96
|
+
The block should return the new relation to use or `nil` to do as if there were no blocks.<br>
|
97
97
|
It's common to use `where_assoc_*(..., &:scope_name)` to use a single scope.
|
98
98
|
* `#where_assoc_count` is a generalization of `#where_assoc_exists` and `#where_assoc_not_exists`. It behaves the same way, but is more powerful, as it allows you to specify how many matches there should be.
|
99
99
|
```ruby
|
100
100
|
# These are equivalent:
|
101
101
|
Post.where_assoc_exists(:comments, is_spam: true)
|
102
|
-
Post.where_assoc_count(
|
102
|
+
Post.where_assoc_count(:comments, :>=, 1, is_spam: true)
|
103
103
|
|
104
104
|
Post.where_assoc_not_exists(:comments, is_spam: true)
|
105
|
-
Post.where_assoc_count(
|
105
|
+
Post.where_assoc_count(:comments, :==, 0, is_spam: true)
|
106
106
|
|
107
107
|
# This has no equivalent (Posts with at least 5 spam comments)
|
108
|
-
Post.where_assoc_count(
|
108
|
+
Post.where_assoc_count(:comments, :>=, 5, is_spam: true)
|
109
109
|
```
|
110
|
-
* `where_assoc_count`'s additional arguments
|
111
|
-
The order of the parameters of `#where_assoc_count` may seem confusing, but you will get used to it. It helps to remember: the goal is to do: `5 < (SELECT COUNT(*) FROM ...)`, the number is first, then operator, then the association and its conditions.
|
112
|
-
* left_operand:
|
113
|
-
* a number
|
114
|
-
* a string of SQL to embed in the query
|
115
|
-
* a range (operator must be `:==` or `:!=`)
|
116
|
-
will use SQL's `BETWEEN` or `NOT BETWEEN`
|
117
|
-
supports infinite ranges and exclusive end
|
118
|
-
* operator: one of `:<`, `:<=`, `:==`, `:!=`, `:>=`, `:>`
|
119
110
|
|
120
111
|
## Intuition
|
121
112
|
|
@@ -127,8 +118,8 @@ Here is the basic intuition for the methods:
|
|
127
118
|
|
128
119
|
`#where_assoc_count` the more specific version of `#where_assoc_exists`. Filters the models, returning those *where* a record for the *association* matching a condition (by default any record in the association) do *not exists*
|
129
120
|
|
130
|
-
The condition that you may need on the record can be quite complicated. For this reason, you can pass a block to these methods.
|
131
|
-
The block will receive a relation on records of the association. Your job is then to call `where` and scopes to specify what you want to exist (or to not exist if using `#where_assoc_not_exists`).
|
121
|
+
The condition that you may need on the record can be quite complicated. For this reason, you can pass a block to these methods.
|
122
|
+
The block will receive a relation on records of the association. Your job is then to call `where` and scopes to specify what you want to exist (or to not exist if using `#where_assoc_not_exists`).
|
132
123
|
|
133
124
|
So if you have `User.where_assoc_exists(:comments) {|rel| rel.where("content ilike '%github.com%'") }`, `rel` is a relation is on `Comment`, and you are specifying what you want to exist. So now we are looking for users that made a comment containing 'github.com'.
|
134
125
|
|
@@ -142,7 +133,7 @@ Sometimes, there isn't a single association that goes deep enough. In that situa
|
|
142
133
|
# Find users that have a post that has a comment that was made by an admin.
|
143
134
|
# Using &:admins to use the admins scope (or any other class method of comments)
|
144
135
|
User.where_assoc_exists(:posts) { |posts|
|
145
|
-
posts.where_assoc_exists(:comments) { |comments|
|
136
|
+
posts.where_assoc_exists(:comments) { |comments|
|
146
137
|
comments.where_assoc_exists(:author, &:admins)
|
147
138
|
}
|
148
139
|
}
|
@@ -223,7 +214,7 @@ On MySQL databases, it is not possible to use `has_one` associations and associa
|
|
223
214
|
|
224
215
|
I do not know of a way to do a SQL query that can deal with all the specifics of `has_one` for MySQL. If you have one, then please suggest it in an issue/pull request.
|
225
216
|
|
226
|
-
In order to work around this, you must use the [ignore_limit](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/RelationReturningMethods.html#module-ActiveRecordWhereAssoc::RelationReturningMethods-label-3Aignore_limit+option) option. The behavior is less correct, but better than being unable to use the gem.
|
217
|
+
In order to work around this, you must use the [ignore_limit](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/RelationReturningMethods.html#module-ActiveRecordWhereAssoc::RelationReturningMethods-label-3Aignore_limit+option) option. The behavior is less correct, but better than being unable to use the gem.
|
227
218
|
|
228
219
|
### has_* :through vs limit/offset
|
229
220
|
For `has_many` and `has_one` with the `:through` option, `#limit` and `#offset` are ignored. Note that `#limit` and `#offset` of the `:source` and of the `:through` side are applied correctly.
|
@@ -101,5 +101,28 @@ module ActiveRecordWhereAssoc
|
|
101
101
|
reflection.is_a?(ActiveRecord::NullRelation)
|
102
102
|
end
|
103
103
|
end
|
104
|
+
|
105
|
+
if ActiveRecord.gem_version >= Gem::Version.new("6.0")
|
106
|
+
def self.indexes(model)
|
107
|
+
model.connection.schema_cache.indexes(model.table_name)
|
108
|
+
end
|
109
|
+
else
|
110
|
+
def self.indexes(model)
|
111
|
+
model.connection.indexes(model.table_name)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
@unique_indexes_cache = {}
|
116
|
+
def self.has_unique_index?(model, column_names)
|
117
|
+
column_names = Array(column_names).map(&:to_s)
|
118
|
+
@unique_indexes_cache.fetch([model, column_names]) do |k|
|
119
|
+
unique_indexes = indexes(model).select(&:unique)
|
120
|
+
columns_names_set = Set.new(column_names)
|
121
|
+
|
122
|
+
# We check for an index whose columns are a subset of the columns we specify
|
123
|
+
# This way, a composite column_names will find uniqueness if just a single of the column is unique
|
124
|
+
@unique_indexes_cache[k] = unique_indexes.any? { |ui| Set.new(ui.columns) <= columns_names_set }
|
125
|
+
end
|
126
|
+
end
|
104
127
|
end
|
105
128
|
end
|
@@ -7,6 +7,17 @@ module ActiveRecordWhereAssoc
|
|
7
7
|
module CoreLogic
|
8
8
|
# Arel table used for aliasing when handling recursive associations (such as parent/children)
|
9
9
|
ALIAS_TABLE = Arel::Table.new("_ar_where_assoc_alias_")
|
10
|
+
OPERATOR_SWAP_OPERANDS = {
|
11
|
+
"<" => ">",
|
12
|
+
"<=" => ">=",
|
13
|
+
">" => "<",
|
14
|
+
">=" => "<=",
|
15
|
+
# Below stays the same
|
16
|
+
"=" => "=",
|
17
|
+
"==" => "==",
|
18
|
+
"!=" => "!=",
|
19
|
+
"<>" => "<>",
|
20
|
+
}
|
10
21
|
|
11
22
|
# Returns the SQL for checking if any of the received relation exists.
|
12
23
|
# Uses a OR if there are multiple relations.
|
@@ -92,7 +103,28 @@ module ActiveRecordWhereAssoc
|
|
92
103
|
# Returns the SQL condition to check if the specified association of the record_class has the desired number of records.
|
93
104
|
#
|
94
105
|
# See RelationReturningMethods#where_assoc_count or SqlReturningMethods#compare_assoc_count_sql for usage details.
|
95
|
-
|
106
|
+
#
|
107
|
+
# The codebase originally only allowed where_assoc_count(5, :<, :comments).
|
108
|
+
# This has since been considered confusing and error prone, with `where_assoc_count(:comments, :>, 5)` being preferable.
|
109
|
+
# So we want to allow the better way to work.
|
110
|
+
# However:
|
111
|
+
# * We originally already allowed any valid SQL to be given as left valid (even a symbol that names a column).
|
112
|
+
# * We don't want this to be a breaking change
|
113
|
+
#
|
114
|
+
# Because of this, it is not simple to correctly auto-detect which situation we are in.
|
115
|
+
# So only the simple and most common case of using a number or a range on the right side (3rd argument) is supported.
|
116
|
+
#
|
117
|
+
# And due to this history, the generated SQL will still have the counting SQL on the right side.
|
118
|
+
def self.compare_assoc_count_sql(record_class, left_assoc_or_value, operator, right_assoc_or_value, given_conditions, options, &block)
|
119
|
+
if right_assoc_or_value.is_a?(Numeric) || right_assoc_or_value.is_a?(Range)
|
120
|
+
association_names = left_assoc_or_value
|
121
|
+
left_operand = right_assoc_or_value
|
122
|
+
operator = OPERATOR_SWAP_OPERANDS.fetch(operator.to_s)
|
123
|
+
else
|
124
|
+
association_names = right_assoc_or_value
|
125
|
+
left_operand = left_assoc_or_value
|
126
|
+
end
|
127
|
+
|
96
128
|
right_sql = only_assoc_count_sql(record_class, association_names, given_conditions, options, &block)
|
97
129
|
|
98
130
|
sql_for_count_operator(left_operand, operator, right_sql)
|
@@ -155,9 +187,17 @@ module ActiveRecordWhereAssoc
|
|
155
187
|
|
156
188
|
init_scopes = initial_scopes_from_reflection(record_class, reflection_chain[i..-1], constraints_chain[i], options)
|
157
189
|
current_scopes = init_scopes.map do |alias_scope, current_scope, klass_scope|
|
158
|
-
current_scope = process_association_step_limits(current_scope, reflection, record_class, options)
|
159
|
-
|
160
190
|
if i.zero?
|
191
|
+
if given_conditions || klass_scope || last_assoc_block || current_scope.offset_value || nest_assocs_block == NestWithSumBlock
|
192
|
+
# In the deepest layer, the limit & offset complexities only matter when:
|
193
|
+
# * There is a condition to apply
|
194
|
+
# * There is an offset (which is a form of filtering)
|
195
|
+
# * We are counting the total matches
|
196
|
+
# Since last_assoc_block is always set except for the deepest association, and is only unset for the deepest layer if
|
197
|
+
# there is no condition given, using it as part of the condition does a lot of work here.
|
198
|
+
current_scope = process_association_step_limits(current_scope, reflection, record_class, options)
|
199
|
+
end
|
200
|
+
|
161
201
|
current_scope = current_scope.where(given_conditions) if given_conditions
|
162
202
|
if klass_scope
|
163
203
|
if klass_scope.respond_to?(:call)
|
@@ -167,6 +207,8 @@ module ActiveRecordWhereAssoc
|
|
167
207
|
end
|
168
208
|
end
|
169
209
|
current_scope = apply_proc_scope(current_scope, last_assoc_block) if last_assoc_block
|
210
|
+
else
|
211
|
+
current_scope = process_association_step_limits(current_scope, reflection, record_class, options)
|
170
212
|
end
|
171
213
|
|
172
214
|
# Those make no sense since at this point, we are only limiting the value that would match using conditions
|
@@ -338,10 +380,18 @@ module ActiveRecordWhereAssoc
|
|
338
380
|
|
339
381
|
current_scope = current_scope.limit(1) if reflection.macro == :has_one
|
340
382
|
|
341
|
-
|
342
|
-
|
383
|
+
if !current_scope.offset_value
|
384
|
+
if current_scope.limit_value
|
385
|
+
join_keys = ActiveRecordCompat.join_keys(reflection, nil)
|
386
|
+
# #join_keys is inverted... the foreign key is on the "source" table, and the key is on the "target" table...
|
387
|
+
# Everything is so complicated in ActiveRecord.
|
388
|
+
current_scope = current_scope.unscope(:limit) if ActiveRecordCompat.has_unique_index?(current_scope.model, join_keys.key)
|
389
|
+
end
|
390
|
+
|
391
|
+
# Order is useless without either limit or offset
|
392
|
+
return current_scope.unscope(:order) if !current_scope.limit_value
|
393
|
+
end
|
343
394
|
|
344
|
-
return current_scope unless current_scope.limit_value || current_scope.offset_value
|
345
395
|
if %w(mysql mysql2).include?(relation_klass.connection.adapter_name.downcase)
|
346
396
|
msg = String.new
|
347
397
|
msg << "Associations and default_scopes with a limit or offset are not supported for MySQL (this includes has_many). "
|
@@ -313,18 +313,18 @@ module ActiveRecordWhereAssoc
|
|
313
313
|
# To clarify, here are equivalent examples:
|
314
314
|
#
|
315
315
|
# Post.where_assoc_exists(:comments)
|
316
|
-
# Post.where_assoc_count(
|
316
|
+
# Post.where_assoc_count(:comments, :>=, 1)
|
317
317
|
#
|
318
318
|
# Post.where_assoc_not_exists(:comments)
|
319
|
-
# Post.where_assoc_count(
|
319
|
+
# Post.where_assoc_count(:comments, :==, 0)
|
320
320
|
#
|
321
|
-
# But these have no equivalent:
|
321
|
+
# But these have no equivalent with the *_exists methods:
|
322
322
|
#
|
323
323
|
# # Posts with at least 5 comments
|
324
|
-
# Post.where_assoc_count(
|
324
|
+
# Post.where_assoc_count(:comments, :>=, 5)
|
325
325
|
#
|
326
326
|
# # Posts with less than 5 comments
|
327
|
-
# Post.where_assoc_count(
|
327
|
+
# Post.where_assoc_count(:comments, :<, 5)
|
328
328
|
#
|
329
329
|
# You could say this is a way of doing a +#select+ that +#count+ the associations of your model
|
330
330
|
# on the SQL side, but faster and more concise.
|
@@ -332,20 +332,30 @@ module ActiveRecordWhereAssoc
|
|
332
332
|
# Examples (with an equivalent ruby +#select+ and +#count+)
|
333
333
|
#
|
334
334
|
# # Posts with at least 5 comments
|
335
|
-
# Post.where_assoc_count(
|
335
|
+
# Post.where_assoc_count(:comments, :>=, 5)
|
336
336
|
# Post.all.select { |post| post.comments.count >= 5 }
|
337
337
|
#
|
338
338
|
# # Posts that have at least 5 comments marked as spam
|
339
|
-
# Post.where_assoc_count(
|
339
|
+
# Post.where_assoc_count(:comments, :>=, 5, is_spam: true)
|
340
340
|
# Post.all.select { |post| post.comments.where(is_spam: true).count >= 5 }
|
341
341
|
#
|
342
342
|
# # Posts that have at least 10 replies spread over their comments
|
343
|
-
# Post.where_assoc_count(
|
344
|
-
# Post.select { |post| post.comments.sum { |comment| comment.replies.count } >=
|
343
|
+
# Post.where_assoc_count([:comments, :replies], :>=, 10)
|
344
|
+
# Post.select { |post| post.comments.sum { |comment| comment.replies.count } >= 10 }
|
345
345
|
#
|
346
|
-
#
|
346
|
+
# Note: Originally, the order of the arguments used to have to be: `where_assoc_count(5, :<, :comments)`.
|
347
|
+
# This was confusing and error prone, with `where_assoc_count(:comments, :>, 5)` appearing preferable.
|
348
|
+
# However, due to the flexibility of what could already be received as first argument, it was not
|
349
|
+
# simple to detect all cases of the arguments being swapped. We didn't want this to be a breaking change.
|
350
|
+
#
|
351
|
+
# So a special case was made for the main case of using a number or a range. This means the only time where
|
352
|
+
# the association_name(s) can be the first argument is when using a number.<br>
|
353
|
+
# Otherwise, the association_names must be the third arguments, such as: where_assoc_count("Some SQL", :<, :comments)
|
354
|
+
#
|
355
|
+
# [left_assoc_or_value]
|
347
356
|
# 1st argument, the left side of the comparison. <br>
|
348
357
|
# One of:
|
358
|
+
# * If using a number or a range as third argument, this can be the association_name(s). (See third parameter)
|
349
359
|
# * a number
|
350
360
|
# * a string of SQL to embed in the query
|
351
361
|
# * a range (operator must be :== or :!=), will use BETWEEN or NOT BETWEEN<br>
|
@@ -353,20 +363,27 @@ module ActiveRecordWhereAssoc
|
|
353
363
|
#
|
354
364
|
# # Posts with 5 to 10 comments
|
355
365
|
# Post.where_assoc_count(5..10, :==, :comments)
|
366
|
+
# Post.where_assoc_count(:comments, :==, 5..10) # Equivalent
|
356
367
|
#
|
357
368
|
# # Posts with less than 5 or more than 10 comments
|
358
369
|
# Post.where_assoc_count(5..10, :!=, :comments)
|
370
|
+
# Post.where_assoc_count(:comments, :!=, 5..10) # Equivalent
|
359
371
|
#
|
360
372
|
# [operator]
|
361
373
|
# The operator to use, one of these symbols: <code> :< :<= :== :!= :>= :> </code>
|
362
374
|
#
|
363
|
-
# [
|
375
|
+
# [right_assoc_or_value]
|
376
|
+
# If the first argument was the association_name(s), then this must be a number or a range.
|
377
|
+
#
|
378
|
+
# Otherwise, this must be the association_names(s).
|
379
|
+
#
|
364
380
|
# The association that must have a certain number of occurrences <br>
|
365
|
-
# Note that if you use an array of association names, the number of the last association
|
381
|
+
# Note that if you use an array of association names, the total number of the last association
|
366
382
|
# is what is counted.
|
367
383
|
#
|
368
384
|
# # Users which have received at least 5 comments total (can be spread on all of their posts)
|
369
385
|
# User.where_assoc_count(5, :<=, [:posts, :comments])
|
386
|
+
# User.where_assoc_count([:posts, :comments], :>=, 5)
|
370
387
|
#
|
371
388
|
# See RelationReturningMethods@Association
|
372
389
|
#
|
@@ -382,26 +399,28 @@ module ActiveRecordWhereAssoc
|
|
382
399
|
# More complex conditions the associated record must match (can also use scopes of the association's model) <br>
|
383
400
|
# See RelationReturningMethods@Block
|
384
401
|
#
|
385
|
-
# The order of the parameters may seem confusing.
|
386
|
-
#
|
402
|
+
# The order of the parameters may seem confusing. That's an mistake that cannot be fixed without a breaking change.
|
403
|
+
# That's why there is now a special case for using a number or range as third argument. Note that the generated SQL will always
|
404
|
+
# place the counting SQL on the right side. (Reversing the operator when needed), like so:
|
387
405
|
# 5 < (SELECT COUNT(*) FROM ...)
|
388
|
-
# So the parameters are in the same order as in that query: number, operator, association.
|
389
406
|
#
|
390
407
|
# To be clear, when you use multiple associations in an array, the count you will be
|
391
408
|
# comparing against is the total number of records of that last association.
|
392
409
|
#
|
393
410
|
# # The users that have received at least 5 comments total on all of their posts
|
394
411
|
# # So this can be from one post that has 5 comments of from 5 posts with 1 comments
|
412
|
+
# User.where_assoc_count([:posts, :comments], :>=, 5)
|
395
413
|
# User.where_assoc_count(5, :<=, [:posts, :comments])
|
396
414
|
#
|
397
415
|
# # The users that have at least 5 posts with at least one comments
|
398
416
|
# User.where_assoc_count(5, :<=, :posts) { where_assoc_exists(:comments) }
|
417
|
+
# User.where_assoc_count(:posts, :>=, 5) { where_assoc_exists(:comments) }
|
399
418
|
#
|
400
419
|
# You can get the SQL string of the condition using SqlReturningMethods#compare_assoc_count_sql.
|
401
420
|
# You can get the SQL string for only the counting using SqlReturningMethods#only_assoc_count_sql.
|
402
|
-
def where_assoc_count(
|
403
|
-
sql = ActiveRecordWhereAssoc::CoreLogic.compare_assoc_count_sql(self.klass,
|
404
|
-
|
421
|
+
def where_assoc_count(left_assoc_or_value, operator, right_assoc_or_value, conditions = nil, options = {}, &block)
|
422
|
+
sql = ActiveRecordWhereAssoc::CoreLogic.compare_assoc_count_sql(self.klass, left_assoc_or_value, operator,
|
423
|
+
right_assoc_or_value, conditions, options, &block)
|
405
424
|
where(sql)
|
406
425
|
end
|
407
426
|
end
|
@@ -45,13 +45,18 @@ module ActiveRecordWhereAssoc
|
|
45
45
|
#
|
46
46
|
# For example:
|
47
47
|
# # Users with at least 10 posts or at least 10 comment
|
48
|
+
# User.where("#{User.compare_assoc_count_sql(:posts, :>=, 10)} OR #{User.compare_assoc_count_sql(:comments, :>=, 10)}")
|
49
|
+
# my_users.where("#{User.compare_assoc_count_sql(:posts, :>=, 10)} OR #{User.compare_assoc_count_sql(:comments, :>=, 10)}")
|
50
|
+
#
|
51
|
+
# # The older way of doing the same thing (the order of parameter used to be reversed,
|
52
|
+
# # both order work when using a number or a range as one of the operand)
|
48
53
|
# User.where("#{User.compare_assoc_count_sql(10, :<=, :posts)} OR #{User.compare_assoc_count_sql(10, :<=, :comments)}")
|
49
54
|
# my_users.where("#{User.compare_assoc_count_sql(10, :<=, :posts)} OR #{User.compare_assoc_count_sql(10, :<=, :comments)}")
|
50
55
|
#
|
51
56
|
# The parameters are the same as RelationReturningMethods#where_assoc_count, including the
|
52
57
|
# possibility of specifying a list of association_name.
|
53
|
-
def compare_assoc_count_sql(
|
54
|
-
ActiveRecordWhereAssoc::CoreLogic.compare_assoc_count_sql(self,
|
58
|
+
def compare_assoc_count_sql(left_assoc_or_value, operator, right_assoc_or_value, conditions = nil, options = {}, &block)
|
59
|
+
ActiveRecordWhereAssoc::CoreLogic.compare_assoc_count_sql(self, left_assoc_or_value, operator, right_assoc_or_value, conditions, options, &block)
|
55
60
|
end
|
56
61
|
|
57
62
|
# This method returns a string containing the SQL to count an association used by RelationReturningMethods#where_assoc_count.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord_where_assoc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maxime Handfield Lapointe
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-03-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -31,9 +31,6 @@ dependencies:
|
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '1.15'
|
34
|
-
- - "<"
|
35
|
-
- !ruby/object:Gem::Version
|
36
|
-
version: '2.6'
|
37
34
|
type: :development
|
38
35
|
prerelease: false
|
39
36
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -41,9 +38,6 @@ dependencies:
|
|
41
38
|
- - ">="
|
42
39
|
- !ruby/object:Gem::Version
|
43
40
|
version: '1.15'
|
44
|
-
- - "<"
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '2.6'
|
47
41
|
- !ruby/object:Gem::Dependency
|
48
42
|
name: minitest
|
49
43
|
requirement: !ruby/object:Gem::Requirement
|