activerecord_where_assoc 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/EXAMPLES.md +129 -77
- data/README.md +32 -15
- data/lib/active_record_where_assoc.rb +11 -8
- data/lib/active_record_where_assoc/core_logic.rb +69 -51
- data/lib/active_record_where_assoc/relation_returning_delegates.rb +12 -0
- data/lib/active_record_where_assoc/{query_methods.rb → relation_returning_methods.rb} +42 -22
- data/lib/active_record_where_assoc/sql_returning_methods.rb +74 -0
- data/lib/active_record_where_assoc/version.rb +1 -1
- metadata +7 -6
- data/lib/active_record_where_assoc/querying.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f470695cdbb7503ff9def8cbe5caca7f229fe2fa133227e78f44a6c6da6156bb
|
4
|
+
data.tar.gz: bcf122af66d2338d345f3e151e060e44a5365b0976bf790e7c066cd9ff05912f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 666127fc2b91da590c71a8f8e95a612ad03597e5e0ee599239752ed83645e93c67de6fcedfbe70a4a9d23f2cf884ff0c6fc091036d752e40f3fffa186ef1e65f
|
7
|
+
data.tar.gz: 582bae5889078d7b259d20ca1e38cd061ccfced8d96e58b2323dfbccdb7ac896fdac1c37626b7d73da17490d350d63949ba73297fd20fc1b3e49fba4cea4870c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# Unreleased
|
2
|
+
|
3
|
+
# 1.1.0 - 2020-02-24
|
4
|
+
|
5
|
+
* Added methods which return the SQL used by this gem: `assoc_exists_sql`, `assoc_not_exists_sql`, `compare_assoc_count_sql`, `only_assoc_count_sql`
|
6
|
+
[Documentation for them](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/SqlReturningMethods.html)
|
7
|
+
|
1
8
|
# 1.0.1
|
2
9
|
|
3
10
|
* Fix broken urls in error messages
|
data/EXAMPLES.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
Here are some example usages of the gem, along with the generated SQL.
|
1
|
+
Here are some example usages of the gem, along with the generated SQL.
|
2
|
+
|
3
|
+
Each of those methods can be chained with scoping methods, so they can be used on `Post`, `my_user.posts`, `Post.where('hello')` or inside a scope. Note that for the `*_sql` variants, those should preferably be used on classes only, because otherwise, it could be confusing for a reader.
|
2
4
|
|
3
5
|
The models can be found in [examples/models.md](examples/models.md). The comments in that file explain how to get a console to try the queries. There are also example uses of the gem for scopes.
|
4
6
|
|
5
|
-
The content of this file is generated
|
7
|
+
The content of this file is generated when running `rake`
|
6
8
|
|
7
9
|
-------
|
8
10
|
|
@@ -14,10 +16,10 @@ Post.where_assoc_exists(:comments)
|
|
14
16
|
```
|
15
17
|
```sql
|
16
18
|
SELECT "posts".* FROM "posts"
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
WHERE (EXISTS (
|
20
|
+
SELECT 1 FROM "comments"
|
21
|
+
WHERE "comments"."post_id" = "posts"."id"
|
22
|
+
))
|
21
23
|
```
|
22
24
|
|
23
25
|
---
|
@@ -28,10 +30,10 @@ Post.where_assoc_not_exists(:comments)
|
|
28
30
|
```
|
29
31
|
```sql
|
30
32
|
SELECT "posts".* FROM "posts"
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
WHERE (NOT EXISTS (
|
34
|
+
SELECT 1 FROM "comments"
|
35
|
+
WHERE "comments"."post_id" = "posts"."id"
|
36
|
+
))
|
35
37
|
```
|
36
38
|
|
37
39
|
---
|
@@ -42,10 +44,10 @@ Post.where_assoc_count(50, :<=, :comments)
|
|
42
44
|
```
|
43
45
|
```sql
|
44
46
|
SELECT "posts".* FROM "posts"
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
WHERE ((50) <= COALESCE((
|
48
|
+
SELECT COUNT(*) FROM "comments"
|
49
|
+
WHERE "comments"."post_id" = "posts"."id"
|
50
|
+
), 0))
|
49
51
|
```
|
50
52
|
|
51
53
|
---
|
@@ -56,10 +58,10 @@ User.where_assoc_exists(:posts)
|
|
56
58
|
```
|
57
59
|
```sql
|
58
60
|
SELECT "users".* FROM "users"
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
61
|
+
WHERE (EXISTS (
|
62
|
+
SELECT 1 FROM "posts"
|
63
|
+
WHERE "posts"."author_id" = "users"."id"
|
64
|
+
))
|
63
65
|
```
|
64
66
|
|
65
67
|
---
|
@@ -70,13 +72,48 @@ User.where_assoc_exists([:posts, :comments])
|
|
70
72
|
```
|
71
73
|
```sql
|
72
74
|
SELECT "users".* FROM "users"
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
))
|
75
|
+
WHERE (EXISTS (
|
76
|
+
SELECT 1 FROM "posts"
|
77
|
+
WHERE "posts"."author_id" = "users"."id" AND (EXISTS (
|
78
|
+
SELECT 1 FROM "comments"
|
79
|
+
WHERE "comments"."post_id" = "posts"."id"
|
79
80
|
))
|
81
|
+
))
|
82
|
+
```
|
83
|
+
|
84
|
+
---
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
# Users with a post or a comment (without using ActiveRecord's `or` method)
|
88
|
+
# Using `my_users` to highlight that *_sql methods should always be called on the class
|
89
|
+
my_users.where("#{User.assoc_exists_sql(:posts)} OR #{User.assoc_exists_sql(:comments)}")
|
90
|
+
```
|
91
|
+
```sql
|
92
|
+
SELECT "users".* FROM "users"
|
93
|
+
WHERE (EXISTS (
|
94
|
+
SELECT 1 FROM "posts"
|
95
|
+
WHERE "posts"."author_id" = "users"."id"
|
96
|
+
) OR EXISTS (
|
97
|
+
SELECT 1 FROM "comments"
|
98
|
+
WHERE "comments"."author_id" = "users"."id"
|
99
|
+
))
|
100
|
+
```
|
101
|
+
|
102
|
+
---
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
# Users with a post or a comment (using ActiveRecord's `or` method)
|
106
|
+
User.where_assoc_exists(:posts).or(User.where_assoc_exists(:comments))
|
107
|
+
```
|
108
|
+
```sql
|
109
|
+
SELECT "users".* FROM "users"
|
110
|
+
WHERE ((EXISTS (
|
111
|
+
SELECT 1 FROM "posts"
|
112
|
+
WHERE "posts"."author_id" = "users"."id"
|
113
|
+
)) OR (EXISTS (
|
114
|
+
SELECT 1 FROM "comments"
|
115
|
+
WHERE "comments"."author_id" = "users"."id"
|
116
|
+
)))
|
80
117
|
```
|
81
118
|
|
82
119
|
---
|
@@ -89,10 +126,10 @@ my_post.comments.where_assoc_exists(:author, is_admin: true)
|
|
89
126
|
```
|
90
127
|
```sql
|
91
128
|
SELECT "comments".* FROM "comments"
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
129
|
+
WHERE "comments"."post_id" = 1 AND (EXISTS (
|
130
|
+
SELECT 1 FROM "users"
|
131
|
+
WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 1
|
132
|
+
))
|
96
133
|
```
|
97
134
|
|
98
135
|
---
|
@@ -103,10 +140,10 @@ my_post.comments.where_assoc_not_exists(:author, &:admins)
|
|
103
140
|
```
|
104
141
|
```sql
|
105
142
|
SELECT "comments".* FROM "comments"
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
143
|
+
WHERE "comments"."post_id" = 1 AND (NOT EXISTS (
|
144
|
+
SELECT 1 FROM "users"
|
145
|
+
WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 1
|
146
|
+
))
|
110
147
|
```
|
111
148
|
|
112
149
|
---
|
@@ -117,10 +154,10 @@ Post.where_assoc_count(5, :<=, :comments, ["is_reported = ?", true])
|
|
117
154
|
```
|
118
155
|
```sql
|
119
156
|
SELECT "posts".* FROM "posts"
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
157
|
+
WHERE ((5) <= COALESCE((
|
158
|
+
SELECT COUNT(*) FROM "comments"
|
159
|
+
WHERE "comments"."post_id" = "posts"."id" AND (is_reported = 1)
|
160
|
+
), 0))
|
124
161
|
```
|
125
162
|
|
126
163
|
---
|
@@ -131,10 +168,10 @@ Post.where_assoc_exists(:author, "is_admin = 't'")
|
|
131
168
|
```
|
132
169
|
```sql
|
133
170
|
SELECT "posts".* FROM "posts"
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
171
|
+
WHERE (EXISTS (
|
172
|
+
SELECT 1 FROM "users"
|
173
|
+
WHERE "users"."id" = "posts"."author_id" AND (is_admin = 't')
|
174
|
+
))
|
138
175
|
```
|
139
176
|
|
140
177
|
---
|
@@ -145,10 +182,10 @@ my_post.comments.where_assoc_not_exists(:author) { admins }
|
|
145
182
|
```
|
146
183
|
```sql
|
147
184
|
SELECT "comments".* FROM "comments"
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
185
|
+
WHERE "comments"."post_id" = 1 AND (NOT EXISTS (
|
186
|
+
SELECT 1 FROM "users"
|
187
|
+
WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 1
|
188
|
+
))
|
152
189
|
```
|
153
190
|
|
154
191
|
---
|
@@ -159,10 +196,10 @@ Post.where_assoc_count(5..10, :==, :comments) { where(is_reported: true) }
|
|
159
196
|
```
|
160
197
|
```sql
|
161
198
|
SELECT "posts".* FROM "posts"
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
199
|
+
WHERE (COALESCE((
|
200
|
+
SELECT COUNT(*) FROM "comments"
|
201
|
+
WHERE "comments"."post_id" = "posts"."id" AND "comments"."is_reported" = 1
|
202
|
+
), 0) BETWEEN 5 AND 10)
|
166
203
|
```
|
167
204
|
|
168
205
|
---
|
@@ -173,10 +210,10 @@ Comment.where_assoc_exists(:post, author_id: my_user.id)
|
|
173
210
|
```
|
174
211
|
```sql
|
175
212
|
SELECT "comments".* FROM "comments"
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
213
|
+
WHERE (EXISTS (
|
214
|
+
SELECT 1 FROM "posts"
|
215
|
+
WHERE "posts"."id" = "comments"."post_id" AND "posts"."author_id" = 1
|
216
|
+
))
|
180
217
|
```
|
181
218
|
|
182
219
|
---
|
@@ -189,13 +226,13 @@ Post.where_assoc_exists([:comments, :author], is_admin: true)
|
|
189
226
|
```
|
190
227
|
```sql
|
191
228
|
SELECT "posts".* FROM "posts"
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
))
|
229
|
+
WHERE (EXISTS (
|
230
|
+
SELECT 1 FROM "comments"
|
231
|
+
WHERE "comments"."post_id" = "posts"."id" AND (EXISTS (
|
232
|
+
SELECT 1 FROM "users"
|
233
|
+
WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 1
|
198
234
|
))
|
235
|
+
))
|
199
236
|
```
|
200
237
|
|
201
238
|
---
|
@@ -206,10 +243,10 @@ Post.where_assoc_exists(:comments, "posts.author_id = comments.author_id")
|
|
206
243
|
```
|
207
244
|
```sql
|
208
245
|
SELECT "posts".* FROM "posts"
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
246
|
+
WHERE (EXISTS (
|
247
|
+
SELECT 1 FROM "comments"
|
248
|
+
WHERE "comments"."post_id" = "posts"."id" AND (posts.author_id = comments.author_id)
|
249
|
+
))
|
213
250
|
```
|
214
251
|
|
215
252
|
---
|
@@ -222,13 +259,13 @@ Post.where_assoc_exists(:comments, is_reported: true) {
|
|
222
259
|
```
|
223
260
|
```sql
|
224
261
|
SELECT "posts".* FROM "posts"
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
))
|
262
|
+
WHERE (EXISTS (
|
263
|
+
SELECT 1 FROM "comments"
|
264
|
+
WHERE "comments"."post_id" = "posts"."id" AND "comments"."is_reported" = 1 AND (EXISTS (
|
265
|
+
SELECT 1 FROM "users"
|
266
|
+
WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 1
|
231
267
|
))
|
268
|
+
))
|
232
269
|
```
|
233
270
|
|
234
271
|
---
|
@@ -240,14 +277,29 @@ my_user.posts.where_assoc_exists(:comments, is_reported: true)
|
|
240
277
|
```
|
241
278
|
```sql
|
242
279
|
SELECT "posts".* FROM "posts"
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
))
|
280
|
+
WHERE "posts"."author_id" = 1 AND (EXISTS (
|
281
|
+
SELECT 1 FROM "comments"
|
282
|
+
WHERE "comments"."post_id" = "posts"."id" AND "comments"."is_reported" = 1
|
283
|
+
)) AND (EXISTS (
|
284
|
+
SELECT 1 FROM "comments"
|
285
|
+
WHERE "comments"."post_id" = "posts"."id" AND (EXISTS (
|
286
|
+
SELECT 1 FROM "users"
|
287
|
+
WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 1
|
252
288
|
))
|
289
|
+
))
|
290
|
+
```
|
291
|
+
```ruby
|
292
|
+
# Users with more posts than comments
|
293
|
+
# Using `my_users` to highlight that *_sql methods should always be called on the class
|
294
|
+
my_users.where("#{User.only_assoc_count_sql(:posts)} > #{User.only_assoc_count_sql(:comments)}")
|
295
|
+
```
|
296
|
+
```sql
|
297
|
+
SELECT "users".* FROM "users"
|
298
|
+
WHERE (COALESCE((
|
299
|
+
SELECT COUNT(*) FROM "posts"
|
300
|
+
WHERE "posts"."author_id" = "users"."id"
|
301
|
+
), 0) > COALESCE((
|
302
|
+
SELECT COUNT(*) FROM "comments"
|
303
|
+
WHERE "comments"."author_id" = "users"."id"
|
304
|
+
), 0))
|
253
305
|
```
|
data/README.md
CHANGED
@@ -39,8 +39,9 @@ These methods have many advantages over the alternative ways of achieving the si
|
|
39
39
|
|
40
40
|
## Installation
|
41
41
|
|
42
|
-
Rails 4.1 to 6.0 are supported with Ruby 2.1 to 2.
|
43
|
-
|
42
|
+
Rails 4.1 to 6.0 are supported with Ruby 2.1 to 2.7.
|
43
|
+
Tested against SQLite3, PostgreSQL and MySQL.
|
44
|
+
The gem only depends on the `activerecord` gem.
|
44
45
|
|
45
46
|
Add this line to your application's Gemfile:
|
46
47
|
|
@@ -56,18 +57,25 @@ Or install it yourself as:
|
|
56
57
|
|
57
58
|
$ gem install activerecord_where_assoc
|
58
59
|
|
60
|
+
## Development state
|
61
|
+
|
62
|
+
This gem is feature complete and production ready.
|
63
|
+
Other than rare tweaks as new versions of Rails and Ruby are released, there shouldn't be much activity on this repository.
|
64
|
+
|
59
65
|
## Documentation
|
60
66
|
|
61
|
-
The [documentation is nicely structured](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/
|
67
|
+
The [documentation is nicely structured](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/RelationReturningMethods.html)
|
68
|
+
|
69
|
+
If you prefer to see it in the code, the main methods are in [this file](https://github.com/MaxLap/activerecord_where_assoc/blob/master/lib/active_record_where_assoc/relation_returning_methods.rb)
|
70
|
+
and the ones that return SQL parts are in [this one](https://github.com/MaxLap/activerecord_where_assoc/blob/master/lib/active_record_where_assoc/sql_returning_methods.rb)
|
62
71
|
|
63
|
-
|
72
|
+
Here are some [usage tips](#usage-tips)
|
64
73
|
|
65
74
|
## Usage
|
66
75
|
|
67
|
-
The [documentation is nicely structured](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/QueryMethods.html)
|
68
76
|
You can view [many examples](EXAMPLES.md).
|
69
77
|
|
70
|
-
Otherwise, here is a short explanation:
|
78
|
+
Otherwise, here is a short explanation of the main methods provided by this gem:
|
71
79
|
|
72
80
|
```ruby
|
73
81
|
where_assoc_exists(association_name, conditions, options, &block)
|
@@ -81,7 +89,7 @@ where_assoc_count(left_operand, operator, association_name, conditions, options,
|
|
81
89
|
* common arguments:
|
82
90
|
* association_name: the association we are doing the condition on.
|
83
91
|
* 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).
|
84
|
-
* options: [available options](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/
|
92
|
+
* options: [available options](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/RelationReturningMethods#module-ActiveRecordWhereAssoc::RelationReturningMethods-label-Options) to alter some behaviors. (rarely necessary)
|
85
93
|
* block: adds more complex conditions by receiving a relation on the association. Can use `#where`, `#where_assoc_*`, scopes, and other scoping methods.
|
86
94
|
Must return a relation.
|
87
95
|
The block either:
|
@@ -171,6 +179,21 @@ Post.where_assoc_exists([:comments, :author, :address], "addresses.country = pos
|
|
171
179
|
|
172
180
|
Doing the same thing but with less associations between `address` and `posts` would not be an issue.
|
173
181
|
|
182
|
+
### Getting SQL strings
|
183
|
+
|
184
|
+
Sometimes, you may need only the SQL of the condition instead of a whole relation, such as when writing your own complex SQL. There are methods available for this use case: `assoc_exists_sql`, `assoc_not_exists_sql`, `compare_assoc_count_sql`, `only_assoc_count_sql`.
|
185
|
+
|
186
|
+
You can read some more about them in [their documentation](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/SqlReturningMethods.html)
|
187
|
+
|
188
|
+
Here is a simple example of they use. Note that they should always be called on the class.
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
# Users with a post or a comment
|
192
|
+
User.where("#{User.assoc_exists_sql(:posts)} OR #{User.assoc_exists_sql(:comments)}")
|
193
|
+
my_users.where("#{User.assoc_exists_sql(:posts)} OR #{User.assoc_exists_sql(:comments)}")
|
194
|
+
# Note that this could be achieved in Rails 5 using the #or method and #where_assoc_exists
|
195
|
+
```
|
196
|
+
|
174
197
|
### The opposite of multiple nested EXISTS...
|
175
198
|
|
176
199
|
... is a single `NOT EXISTS` with the nested ones still using `EXISTS`.
|
@@ -179,7 +202,7 @@ All the methods always chain nested associations using an `EXISTS` when they hav
|
|
179
202
|
|
180
203
|
### Using `#from` in scope
|
181
204
|
|
182
|
-
If you want to use a scope / condition which uses `#from`, then you need to use the [:never_alias_limit](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/
|
205
|
+
If you want to use a scope / condition which uses `#from`, then you need to use the [:never_alias_limit](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/RelationReturningMethods.html#module-ActiveRecordWhereAssoc::RelationReturningMethods-label-3Anever_alias_limit+option) option to avoid `#where_assoc_*` being overwritten by your scope and getting a weird exception / wrong result.
|
183
206
|
|
184
207
|
## Known issues/limitations
|
185
208
|
|
@@ -188,7 +211,7 @@ On MySQL databases, it is not possible to use `has_one` associations and associa
|
|
188
211
|
|
189
212
|
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.
|
190
213
|
|
191
|
-
In order to work around this, you must use the [ignore_limit](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/
|
214
|
+
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.
|
192
215
|
|
193
216
|
### has_* :through vs limit/offset
|
194
217
|
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.
|
@@ -209,12 +232,6 @@ Run `bin/console` for an interactive prompt that will allow you to experiment in
|
|
209
232
|
|
210
233
|
Run `bin/fixcop` to fix a lot of common styling mistake from your changes and then display the remaining rubocop rules you break. Make sure to do this before committing and submitting PRs. Use common sense, sometimes it's okay to break a rule, add a [rubocop:disable comment](http://rubocop.readthedocs.io/en/latest/configuration/#disabling-cops-within-source-code) in that situation.
|
211
234
|
|
212
|
-
Run `bin/testall` to test all supported rails/ruby versions:
|
213
|
-
* It will tell you about missing ruby versions, which you can install if you want to test for them
|
214
|
-
* It will run `rake test` on each supported version or ruby/rails
|
215
|
-
* It automatically installs bundler if a ruby version doesn't have it
|
216
|
-
* It automatically runs `bundle install`
|
217
|
-
|
218
235
|
## Contributing
|
219
236
|
|
220
237
|
Bug reports and pull requests are welcome on GitHub at https://github.com/MaxLap/activerecord_where_assoc.
|
@@ -7,12 +7,13 @@ module ActiveRecordWhereAssoc
|
|
7
7
|
# Default options for the gem. Meant to be modified in place by external code, such as in
|
8
8
|
# an initializer.
|
9
9
|
# Ex:
|
10
|
-
# ActiveRecordWhereAssoc[:ignore_limit] = true
|
10
|
+
# ActiveRecordWhereAssoc.default_options[:ignore_limit] = true
|
11
11
|
#
|
12
|
-
# A description for each can be found in
|
12
|
+
# A description for each can be found in RelationReturningMethods@Options.
|
13
13
|
#
|
14
|
-
#
|
15
|
-
#
|
14
|
+
# :ignore_limit is the only one to consider changing, when you are using MySQL, since limit are
|
15
|
+
# never supported on it. Otherwise, the safety of having to pass the options yourself
|
16
|
+
# and noticing you made a mistake / avoiding the need for extra queries is worth the extra code.
|
16
17
|
def self.default_options
|
17
18
|
@default_options ||= {
|
18
19
|
ignore_limit: false,
|
@@ -23,12 +24,14 @@ module ActiveRecordWhereAssoc
|
|
23
24
|
end
|
24
25
|
|
25
26
|
require_relative "active_record_where_assoc/core_logic"
|
26
|
-
require_relative "active_record_where_assoc/
|
27
|
-
require_relative "active_record_where_assoc/
|
27
|
+
require_relative "active_record_where_assoc/relation_returning_methods"
|
28
|
+
require_relative "active_record_where_assoc/relation_returning_delegates"
|
29
|
+
require_relative "active_record_where_assoc/sql_returning_methods"
|
28
30
|
|
29
31
|
ActiveSupport.on_load(:active_record) do
|
30
32
|
ActiveRecord.eager_load!
|
31
33
|
|
32
|
-
ActiveRecord::Relation.include(ActiveRecordWhereAssoc::
|
33
|
-
ActiveRecord::Base.extend(ActiveRecordWhereAssoc::
|
34
|
+
ActiveRecord::Relation.include(ActiveRecordWhereAssoc::RelationReturningMethods)
|
35
|
+
ActiveRecord::Base.extend(ActiveRecordWhereAssoc::RelationReturningDelegates)
|
36
|
+
ActiveRecord::Base.extend(ActiveRecordWhereAssoc::SqlReturningMethods)
|
34
37
|
end
|
@@ -8,25 +8,37 @@ module ActiveRecordWhereAssoc
|
|
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
10
|
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
sql =
|
17
|
-
|
18
|
-
|
11
|
+
# Returns the SQL for checking if any of the received relation exists.
|
12
|
+
# Uses a OR if there are multiple relations.
|
13
|
+
# => "EXISTS (SELECT... *relation1*) OR EXISTS (SELECT... *relation2*)"
|
14
|
+
def self.sql_for_any_exists(relations)
|
15
|
+
relations = [relations] unless relations.is_a?(Array)
|
16
|
+
sql = relations.map { |ns| "EXISTS (#{ns.select('1').to_sql})" }.join(" OR ")
|
17
|
+
if relations.size > 1
|
18
|
+
"(#{sql})" # Needed when embedding the sql in a `where`, because the OR could make things wrong
|
19
|
+
elsif relations.size == 1
|
20
|
+
sql
|
21
|
+
else
|
22
|
+
"0=1"
|
23
|
+
end
|
24
|
+
end
|
19
25
|
|
20
|
-
|
26
|
+
# Block used when nesting associations for a where_assoc_[not_]exists
|
27
|
+
NestWithExistsBlock = lambda do |wrapping_scope, nested_scopes|
|
28
|
+
wrapping_scope.where(sql_for_any_exists(nested_scopes))
|
21
29
|
end
|
22
30
|
|
23
|
-
#
|
24
|
-
#
|
25
|
-
|
31
|
+
# Returns the SQL for getting the sum of of the received relations
|
32
|
+
# => "SUM((SELECT... *relation1*)) + SUM((SELECT... *relation2*))"
|
33
|
+
def self.sql_for_sum_of_counts(nested_scopes)
|
26
34
|
nested_scopes = [nested_scopes] unless nested_scopes.is_a?(Array)
|
27
35
|
# Need the double parentheses
|
28
|
-
|
36
|
+
nested_scopes.map { |ns| "SUM((#{ns.to_sql}))" }.join(" + ").presence || "0"
|
37
|
+
end
|
29
38
|
|
39
|
+
# Block used when nesting associations for a where_assoc_count
|
40
|
+
NestWithSumBlock = lambda do |wrapping_scope, nested_scopes|
|
41
|
+
sql = sql_for_sum_of_counts(nested_scopes)
|
30
42
|
wrapping_scope.unscope(:select).select(sql)
|
31
43
|
end
|
32
44
|
|
@@ -43,72 +55,78 @@ module ActiveRecordWhereAssoc
|
|
43
55
|
options.fetch(key) { ActiveRecordWhereAssoc.default_options[key] }
|
44
56
|
end
|
45
57
|
|
46
|
-
# Returns
|
47
|
-
# based on if a record for the specified association of the model exists.
|
58
|
+
# Returns the SQL condition to check if the specified association of the record_class exists (has records).
|
48
59
|
#
|
49
|
-
# See #where_assoc_exists
|
50
|
-
def self.
|
51
|
-
nested_relations = relations_on_association(
|
52
|
-
|
60
|
+
# See RelationReturningMethods#where_assoc_exists or SqlReturningMethods#assoc_exists_sql for usage details.
|
61
|
+
def self.assoc_exists_sql(record_class, association_names, given_conditions, options, &block)
|
62
|
+
nested_relations = relations_on_association(record_class, association_names, given_conditions, options, block, NestWithExistsBlock)
|
63
|
+
sql_for_any_exists(nested_relations)
|
53
64
|
end
|
54
65
|
|
55
|
-
# Returns
|
56
|
-
# based on if a record for the specified association of the model doesn't exist.
|
66
|
+
# Returns the SQL condition to check if the specified association of the record_class doesn't exist (has no records).
|
57
67
|
#
|
58
|
-
# See #
|
59
|
-
def self.
|
60
|
-
nested_relations = relations_on_association(
|
61
|
-
|
68
|
+
# See RelationReturningMethods#where_assoc_not_exists or SqlReturningMethods#assoc_not_exists_sql for usage details.
|
69
|
+
def self.assoc_not_exists_sql(record_class, association_names, given_conditions, options, &block)
|
70
|
+
nested_relations = relations_on_association(record_class, association_names, given_conditions, options, block, NestWithExistsBlock)
|
71
|
+
"NOT #{sql_for_any_exists(nested_relations)}"
|
62
72
|
end
|
63
73
|
|
64
|
-
#
|
65
|
-
#
|
74
|
+
# This does not return an SQL condition. Instead, it returns only the SQL to count the number of records for the specified
|
75
|
+
# association.
|
66
76
|
#
|
67
|
-
# See #
|
68
|
-
def self.
|
77
|
+
# See SqlReturningMethods#only_assoc_count_sql for usage details.
|
78
|
+
def self.only_assoc_count_sql(record_class, association_names, given_conditions, options, &block)
|
69
79
|
deepest_scope_mod = lambda do |deepest_scope|
|
70
80
|
deepest_scope = apply_proc_scope(deepest_scope, block) if block
|
71
81
|
|
72
82
|
deepest_scope.unscope(:select).select("COUNT(*)")
|
73
83
|
end
|
74
84
|
|
75
|
-
nested_relations = relations_on_association(
|
85
|
+
nested_relations = relations_on_association(record_class, association_names, given_conditions, options, deepest_scope_mod, NestWithSumBlock)
|
86
|
+
|
87
|
+
nested_relations.map { |nr| "COALESCE((#{nr.to_sql}), 0)" }.join(" + ").presence || "0"
|
88
|
+
end
|
76
89
|
|
77
|
-
|
90
|
+
# Returns the SQL condition to check if the specified association of the record_class has the desired number of records.
|
91
|
+
#
|
92
|
+
# See RelationReturningMethods#where_assoc_count or SqlReturningMethods#compare_assoc_count_sql for usage details.
|
93
|
+
def self.compare_assoc_count_sql(record_class, left_operand, operator, association_names, given_conditions, options, &block)
|
94
|
+
right_sql = only_assoc_count_sql(record_class, association_names, given_conditions, options, &block)
|
78
95
|
|
79
|
-
|
80
|
-
base_relation.where(sql)
|
96
|
+
sql_for_count_operator(left_operand, operator, right_sql)
|
81
97
|
end
|
82
98
|
|
83
99
|
# Returns relations on the associated model meant to be embedded in a query
|
84
|
-
# Will return more than one association
|
85
|
-
#
|
86
|
-
def self.relations_on_association(
|
100
|
+
# Will only return more than one association when there are polymorphic belongs_to
|
101
|
+
# association_names: can be an array of association names or a single one
|
102
|
+
def self.relations_on_association(record_class, association_names, given_conditions, options, last_assoc_block, nest_assocs_block)
|
87
103
|
validate_options(options)
|
88
|
-
|
104
|
+
association_names = Array.wrap(association_names)
|
105
|
+
_relations_on_association_recurse(record_class, association_names, given_conditions, options, last_assoc_block, nest_assocs_block)
|
106
|
+
end
|
89
107
|
|
90
|
-
|
108
|
+
def self._relations_on_association_recurse(record_class, association_names, given_conditions, options, last_assoc_block, nest_assocs_block)
|
109
|
+
if association_names.size > 1
|
91
110
|
recursive_scope_block = lambda do |scope|
|
92
|
-
nested_scope =
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
111
|
+
nested_scope = _relations_on_association_recurse(scope,
|
112
|
+
association_names[1..-1],
|
113
|
+
given_conditions,
|
114
|
+
options,
|
115
|
+
last_assoc_block,
|
116
|
+
nest_assocs_block)
|
98
117
|
nest_assocs_block.call(scope, nested_scope)
|
99
118
|
end
|
100
119
|
|
101
|
-
relations_on_one_association(
|
120
|
+
relations_on_one_association(record_class, association_names.first, nil, options, recursive_scope_block, nest_assocs_block)
|
102
121
|
else
|
103
|
-
relations_on_one_association(
|
122
|
+
relations_on_one_association(record_class, association_names.first, given_conditions, options, last_assoc_block, nest_assocs_block)
|
104
123
|
end
|
105
124
|
end
|
106
125
|
|
107
126
|
# Returns relations on the associated model meant to be embedded in a query
|
108
127
|
# Will return more than one association only for polymorphic belongs_to
|
109
|
-
def self.relations_on_one_association(
|
110
|
-
|
111
|
-
final_reflection = fetch_reflection(relation_klass, association_name)
|
128
|
+
def self.relations_on_one_association(record_class, association_name, given_conditions, options, last_assoc_block, nest_assocs_block)
|
129
|
+
final_reflection = fetch_reflection(record_class, association_name)
|
112
130
|
|
113
131
|
check_reflection_validity!(final_reflection)
|
114
132
|
|
@@ -135,7 +153,7 @@ module ActiveRecordWhereAssoc
|
|
135
153
|
|
136
154
|
init_scopes = initial_scopes_from_reflection(reflection_chain[i..-1], constaints_chain[i], options)
|
137
155
|
current_scopes = init_scopes.map do |alias_scope, current_scope, klass_scope|
|
138
|
-
current_scope = process_association_step_limits(current_scope, reflection,
|
156
|
+
current_scope = process_association_step_limits(current_scope, reflection, record_class, options)
|
139
157
|
|
140
158
|
if i.zero?
|
141
159
|
current_scope = current_scope.where(given_conditions) if given_conditions
|
@@ -284,7 +302,7 @@ module ActiveRecordWhereAssoc
|
|
284
302
|
end
|
285
303
|
msg << "This is not supported by ActiveRecord when doing joins, but it is by WhereAssoc. However, "
|
286
304
|
msg << "you must pass the :poly_belongs_to option to specify what to do in this case.\n"
|
287
|
-
msg << "See https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/
|
305
|
+
msg << "See https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/RelationReturningMethods.html#module-ActiveRecordWhereAssoc::RelationReturningMethods-label-3Apoly_belongs_to+option"
|
288
306
|
raise ActiveRecordWhereAssoc::PolymorphicBelongsToWithoutClasses, msg
|
289
307
|
else
|
290
308
|
if on_poly_belongs_to.is_a?(Class) && on_poly_belongs_to < ActiveRecord::Base
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Needed for delegate
|
4
|
+
require "active_support"
|
5
|
+
|
6
|
+
module ActiveRecordWhereAssoc
|
7
|
+
module RelationReturningDelegates
|
8
|
+
# Delegating the methods in RelationReturningMethods from ActiveRecord::Base to :all. Same thing ActiveRecord does for #where.
|
9
|
+
new_relation_returning_methods = RelationReturningMethods.public_instance_methods
|
10
|
+
delegate(*new_relation_returning_methods, to: :all)
|
11
|
+
end
|
12
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# See
|
3
|
+
# See RelationReturningMethods
|
4
4
|
module ActiveRecordWhereAssoc
|
5
5
|
# This module adds new variations of +#where+ to your Models/relations/associations/scopes.
|
6
6
|
# These variations check if an association has records, so you can check if a +Post+ has
|
@@ -18,11 +18,20 @@ module ActiveRecordWhereAssoc
|
|
18
18
|
# * scopes: (On the Post model) <tt>scope :with_comments, -> { where_assoc_exists(:comments) }</tt>
|
19
19
|
# * models: <tt>Post.where_assoc_exists(:comments)</tt>
|
20
20
|
#
|
21
|
-
#
|
22
|
-
#
|
21
|
+
# In short: Anywhere you could use #where, you can also use the methods presented here. This includes
|
22
|
+
# with ActiveRecord's #or method.
|
23
23
|
#
|
24
|
-
#
|
25
|
-
#
|
24
|
+
# {Introduction to this gem}[https://github.com/MaxLap/activerecord_where_assoc/blob/master/INTRODUCTION.md]
|
25
|
+
# introduces each features of the gem clearly.
|
26
|
+
#
|
27
|
+
# The {gem's README.md}[https://github.com/MaxLap/activerecord_where_assoc/blob/master/README.md] contains
|
28
|
+
# known limitations and usage tips.
|
29
|
+
#
|
30
|
+
# {Many examples}[https://github.com/MaxLap/activerecord_where_assoc/blob/master/EXAMPLES.md] are available,
|
31
|
+
# including the generated SQL queries.
|
32
|
+
#
|
33
|
+
# If you need extra convincing to try this gem, this document with the problems of the other
|
34
|
+
# ways of doing this kind of filtering should help:
|
26
35
|
# {alternatives' problems}[https://github.com/MaxLap/activerecord_where_assoc/blob/master/ALTERNATIVES_PROBLEMS.md].
|
27
36
|
#
|
28
37
|
# === Association
|
@@ -182,7 +191,10 @@ module ActiveRecordWhereAssoc
|
|
182
191
|
# It's also possible that you only want to search for those that match some specific Models, ignoring the other ones.
|
183
192
|
# [:pluck]
|
184
193
|
# Do a +#pluck+ in the column to detect to possible choices. This option can have a performance cost for big tables
|
185
|
-
# or when the query if done often, as the +#pluck+ will be executed each time
|
194
|
+
# or when the query if done often, as the +#pluck+ will be executed each time.
|
195
|
+
# It is important to note that this happens when the query is prepared, so using this with methods that return SQL
|
196
|
+
# (such as SqlReturningMethods#assoc_exists_sql) will still execute a query even if you don't
|
197
|
+
# use the returned string.
|
186
198
|
# [model or array of models]
|
187
199
|
# Specify which models to search for. This avoids the performance cost of +#pluck+ and can allow to filter some
|
188
200
|
# of the choices out that don't interest you. <br>
|
@@ -196,7 +208,7 @@ module ActiveRecordWhereAssoc
|
|
196
208
|
# Computer => proc { brand_new.where(core: 4) } })
|
197
209
|
# [:raise]
|
198
210
|
# (default) raise an exception when a polymorphic belongs_to is encountered.
|
199
|
-
module
|
211
|
+
module RelationReturningMethods
|
200
212
|
# :section: Basic methods
|
201
213
|
|
202
214
|
# Returns a new relation with a condition added (a +#where+) that checks if an association
|
@@ -221,22 +233,24 @@ module ActiveRecordWhereAssoc
|
|
221
233
|
#
|
222
234
|
# [association_name]
|
223
235
|
# The association that must exist <br>
|
224
|
-
# See
|
236
|
+
# See RelationReturningMethods@Association
|
225
237
|
#
|
226
238
|
# [condition]
|
227
239
|
# Extra conditions the association must match <br>
|
228
|
-
# See
|
240
|
+
# See RelationReturningMethods@Condition
|
229
241
|
#
|
230
242
|
# [options]
|
231
243
|
# Options to alter the generated query <br>
|
232
|
-
# See
|
244
|
+
# See RelationReturningMethods@Options
|
233
245
|
#
|
234
246
|
# [&block]
|
235
247
|
# More complex conditions the associated record must match (can also use scopes of the association's model) <br>
|
236
|
-
# See
|
248
|
+
# See RelationReturningMethods@Block
|
237
249
|
#
|
250
|
+
# You can get the SQL string of the condition using SqlReturningMethods#assoc_exists_sql.
|
238
251
|
def where_assoc_exists(association_name, conditions = nil, options = {}, &block)
|
239
|
-
ActiveRecordWhereAssoc::CoreLogic.
|
252
|
+
sql = ActiveRecordWhereAssoc::CoreLogic.assoc_exists_sql(self.klass, association_name, conditions, options, &block)
|
253
|
+
where(sql)
|
240
254
|
end
|
241
255
|
|
242
256
|
# Returns a new relation with a condition added (a +#where+) that checks if an association
|
@@ -265,22 +279,24 @@ module ActiveRecordWhereAssoc
|
|
265
279
|
#
|
266
280
|
# [association_name]
|
267
281
|
# The association that must exist <br>
|
268
|
-
# See
|
282
|
+
# See RelationReturningMethods@Association
|
269
283
|
#
|
270
284
|
# [condition]
|
271
285
|
# Extra conditions the association must not match <br>
|
272
|
-
# See
|
286
|
+
# See RelationReturningMethods@Condition
|
273
287
|
#
|
274
288
|
# [options]
|
275
289
|
# Options to alter the generated query <br>
|
276
|
-
# See
|
290
|
+
# See RelationReturningMethods@Options
|
277
291
|
#
|
278
292
|
# [&block]
|
279
293
|
# More complex conditions the associated record must match (can also use scopes of the association's model) <br>
|
280
|
-
# See
|
294
|
+
# See RelationReturningMethods@Block
|
281
295
|
#
|
296
|
+
# You can get the SQL string of the condition using SqlReturningMethods#assoc_not_exists_sql.
|
282
297
|
def where_assoc_not_exists(association_name, conditions = nil, options = {}, &block)
|
283
|
-
ActiveRecordWhereAssoc::CoreLogic.
|
298
|
+
sql = ActiveRecordWhereAssoc::CoreLogic.assoc_not_exists_sql(self.klass, association_name, conditions, options, &block)
|
299
|
+
where(sql)
|
284
300
|
end
|
285
301
|
|
286
302
|
# :section: Complex method
|
@@ -348,19 +364,19 @@ module ActiveRecordWhereAssoc
|
|
348
364
|
# # Users which have received at least 5 comments total (can be spread on all of their posts)
|
349
365
|
# User.where_assoc_count(5, :<=, [:posts, :comments])
|
350
366
|
#
|
351
|
-
# See
|
367
|
+
# See RelationReturningMethods@Association
|
352
368
|
#
|
353
369
|
# [condition]
|
354
370
|
# Extra conditions the association must match to count <br>
|
355
|
-
# See
|
371
|
+
# See RelationReturningMethods@Condition
|
356
372
|
#
|
357
373
|
# [options]
|
358
374
|
# Options to alter the generated query <br>
|
359
|
-
# See
|
375
|
+
# See RelationReturningMethods@Options
|
360
376
|
#
|
361
377
|
# [&block]
|
362
378
|
# More complex conditions the associated record must match (can also use scopes of the association's model) <br>
|
363
|
-
# See
|
379
|
+
# See RelationReturningMethods@Block
|
364
380
|
#
|
365
381
|
# The order of the parameters may seem confusing. But you will get used to it. It helps
|
366
382
|
# to remember that the goal is to do:
|
@@ -377,8 +393,12 @@ module ActiveRecordWhereAssoc
|
|
377
393
|
# # The users that have at least 5 posts with at least one comments
|
378
394
|
# User.where_assoc_count(5, :<=, :posts) { where_assoc_exists(:comments) }
|
379
395
|
#
|
396
|
+
# You can get the SQL string of the condition using SqlReturningMethods#compare_assoc_count_sql.
|
397
|
+
# You can get the SQL string for only the counting using SqlReturningMethods#only_assoc_count_sql.
|
380
398
|
def where_assoc_count(left_operand, operator, association_name, conditions = nil, options = {}, &block)
|
381
|
-
ActiveRecordWhereAssoc::CoreLogic.
|
399
|
+
sql = ActiveRecordWhereAssoc::CoreLogic.compare_assoc_count_sql(self.klass, left_operand, operator,
|
400
|
+
association_name, conditions, options, &block)
|
401
|
+
where(sql)
|
382
402
|
end
|
383
403
|
end
|
384
404
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordWhereAssoc
|
4
|
+
# The methods in this module return partial SQL queries. These are used by the main methods of
|
5
|
+
# this gem: the #where_assoc_* methods located in RelationReturningMethods. But in some situation, the SQL strings can be useful to
|
6
|
+
# do complex manual queries by embedding them in your own SQL code.
|
7
|
+
#
|
8
|
+
# Those methods should be used directly on your model's class. You can use them from a relation, but the result will be
|
9
|
+
# the same, so your intent will be clearer by doing it on the class directly.
|
10
|
+
#
|
11
|
+
# # This is the recommended way:
|
12
|
+
# sql = User.assoc_exists_sql(:posts)
|
13
|
+
#
|
14
|
+
# # While this also works, it may be confusing when reading the code:
|
15
|
+
# sql = my_filtered_users.assoc_exists_sql(:posts)
|
16
|
+
# # the sql variable is not affected by my_filtered_users.
|
17
|
+
module SqlReturningMethods
|
18
|
+
# This method returns a string containing the SQL condition used by RelationReturningMethods#where_assoc_exists.
|
19
|
+
# You can pass that SQL string directly to #where to get the same result as RelationReturningMethods#where_assoc_exists.
|
20
|
+
# This can be useful to get the SQL of an EXISTS query for use in your own SQL code.
|
21
|
+
#
|
22
|
+
# For example:
|
23
|
+
# # Users with a post or a comment
|
24
|
+
# User.where("#{User.assoc_exists_sql(:posts)} OR #{User.assoc_exists_sql(:comments)}")
|
25
|
+
# my_users.where("#{User.assoc_exists_sql(:posts)} OR #{User.assoc_exists_sql(:comments)}")
|
26
|
+
#
|
27
|
+
# The parameters are the same as RelationReturningMethods#where_assoc_exists, including the
|
28
|
+
# possibility of specifying a list of association_name.
|
29
|
+
def assoc_exists_sql(association_name, conditions = nil, options = {}, &block)
|
30
|
+
ActiveRecordWhereAssoc::CoreLogic.assoc_exists_sql(self, association_name, conditions, options, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
# This method generates the SQL query used by RelationReturningMethods#where_assoc_not_exists.
|
34
|
+
# This method is the same as #assoc_exists_sql, but for RelationReturningMethods#where_assoc_not_exists.
|
35
|
+
#
|
36
|
+
# The parameters are the same as RelationReturningMethods#where_assoc_not_exists, including the
|
37
|
+
# possibility of specifying a list of association_name.
|
38
|
+
def assoc_not_exists_sql(association_name, conditions = nil, options = {}, &block)
|
39
|
+
ActiveRecordWhereAssoc::CoreLogic.assoc_not_exists_sql(self, association_name, conditions, options, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
# This method returns a string containing the SQL condition used by RelationReturningMethods#where_assoc_count.
|
43
|
+
# You can pass that SQL string directly to #where to get the same result as RelationReturningMethods#where_assoc_count.
|
44
|
+
# This can be useful to get the SQL query to compare the count of an association for use in your own SQL code.
|
45
|
+
#
|
46
|
+
# For example:
|
47
|
+
# # Users with at least 10 posts or at least 10 comment
|
48
|
+
# User.where("#{User.compare_assoc_count_sql(10, :<=, :posts)} OR #{User.compare_assoc_count_sql(10, :<=, :comments)}")
|
49
|
+
# my_users.where("#{User.compare_assoc_count_sql(10, :<=, :posts)} OR #{User.compare_assoc_count_sql(10, :<=, :comments)}")
|
50
|
+
#
|
51
|
+
# The parameters are the same as RelationReturningMethods#where_assoc_count, including the
|
52
|
+
# possibility of specifying a list of association_name.
|
53
|
+
def compare_assoc_count_sql(left_operand, operator, association_name, conditions = nil, options = {}, &block)
|
54
|
+
ActiveRecordWhereAssoc::CoreLogic.compare_assoc_count_sql(self, left_operand, operator, association_name, conditions, options, &block)
|
55
|
+
end
|
56
|
+
|
57
|
+
# This method returns a string containing the SQL to count an association used by RelationReturningMethods#where_assoc_count.
|
58
|
+
# The returned SQL does not do a comparison, only the counting part. So you can do the comparison yourself.
|
59
|
+
# This can be useful to get the SQL to count the an association query for use in your own SQL code.
|
60
|
+
#
|
61
|
+
# For example:
|
62
|
+
# # Users with more posts than comments
|
63
|
+
# User.where("#{User.only_assoc_count_sql(:posts)} > #{User.only_assoc_count_sql(:comments)}")
|
64
|
+
# my_users.where("#{User.only_assoc_count_sql(:posts)} > #{User.only_assoc_count_sql(:comments)}")
|
65
|
+
#
|
66
|
+
# Since the comparison is not made by this method, the first 2 parameters (left_operand and operator)
|
67
|
+
# of RelationReturningMethods#where_assoc_count are not accepted by this method. The remaining
|
68
|
+
# parameters of RelationReturningMethods#where_assoc_count are accepted, which are the same
|
69
|
+
# the same as those of RelationReturningMethods#where_assoc_exists.
|
70
|
+
def only_assoc_count_sql(association_name, conditions = nil, options = {}, &block)
|
71
|
+
ActiveRecordWhereAssoc::CoreLogic.only_assoc_count_sql(self, association_name, conditions, options, &block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
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.0
|
4
|
+
version: 1.1.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: 2020-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -70,14 +70,14 @@ dependencies:
|
|
70
70
|
name: rake
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '10.0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '10.0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
@@ -180,8 +180,9 @@ files:
|
|
180
180
|
- lib/active_record_where_assoc/active_record_compat.rb
|
181
181
|
- lib/active_record_where_assoc/core_logic.rb
|
182
182
|
- lib/active_record_where_assoc/exceptions.rb
|
183
|
-
- lib/active_record_where_assoc/
|
184
|
-
- lib/active_record_where_assoc/
|
183
|
+
- lib/active_record_where_assoc/relation_returning_delegates.rb
|
184
|
+
- lib/active_record_where_assoc/relation_returning_methods.rb
|
185
|
+
- lib/active_record_where_assoc/sql_returning_methods.rb
|
185
186
|
- lib/active_record_where_assoc/version.rb
|
186
187
|
- lib/activerecord_where_assoc.rb
|
187
188
|
homepage: https://github.com/MaxLap/activerecord_where_assoc
|