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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 455c7e8523f81f043fe2933c78e54e3799f66f01bff81dd34d21185c3b268aaf
4
- data.tar.gz: 1895e78079d3f9f06f532e01dcd7313a9c7fa27e3f38ec411359a1352fd755db
3
+ metadata.gz: f470695cdbb7503ff9def8cbe5caca7f229fe2fa133227e78f44a6c6da6156bb
4
+ data.tar.gz: bcf122af66d2338d345f3e151e060e44a5365b0976bf790e7c066cd9ff05912f
5
5
  SHA512:
6
- metadata.gz: fecba216f37489edaaf31945436228781fae1a102a336e93e3958b47305ca04493981b6c8291743cdfd54db236560a2c3027cae61c4988997f573368b3ad7770
7
- data.tar.gz: d2011c3dbee23ab3244df38e2069a5bbac4ac8c062ed4028af3090bb91222f31f98132bc62e5db0804af34736310b9d1e763dc82b5cc99b1d231fb24c1553e9e
6
+ metadata.gz: 666127fc2b91da590c71a8f8e95a612ad03597e5e0ee599239752ed83645e93c67de6fcedfbe70a4a9d23f2cf884ff0c6fc091036d752e40f3fffa186ef1e65f
7
+ data.tar.gz: 582bae5889078d7b259d20ca1e38cd061ccfced8d96e58b2323dfbccdb7ac896fdac1c37626b7d73da17490d350d63949ba73297fd20fc1b3e49fba4cea4870c
@@ -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
@@ -1,8 +1,10 @@
1
- Here are some example usages of the gem, along with the generated SQL. Each of those can be chained with scoping methods.
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 from running `ruby examples/examples.rb > EXAMPLES.md`
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
- WHERE (EXISTS (
18
- SELECT 1 FROM "comments"
19
- WHERE "comments"."post_id" = "posts"."id"
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
- WHERE (NOT EXISTS (
32
- SELECT 1 FROM "comments"
33
- WHERE "comments"."post_id" = "posts"."id"
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
- WHERE ((50) <= COALESCE((
46
- SELECT COUNT(*) FROM "comments"
47
- WHERE "comments"."post_id" = "posts"."id"
48
- ), 0))
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
- WHERE (EXISTS (
60
- SELECT 1 FROM "posts"
61
- WHERE "posts"."author_id" = "users"."id"
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
- WHERE (EXISTS (
74
- SELECT 1 FROM "posts"
75
- WHERE "posts"."author_id" = "users"."id" AND (EXISTS (
76
- SELECT 1 FROM "comments"
77
- WHERE "comments"."post_id" = "posts"."id"
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
- WHERE "comments"."post_id" = 1 AND (EXISTS (
93
- SELECT 1 FROM "users"
94
- WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 't'
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
- WHERE "comments"."post_id" = 1 AND (NOT EXISTS (
107
- SELECT 1 FROM "users"
108
- WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 't'
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
- WHERE ((5) <= COALESCE((
121
- SELECT COUNT(*) FROM "comments"
122
- WHERE "comments"."post_id" = "posts"."id" AND (is_reported = 't')
123
- ), 0))
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
- WHERE (EXISTS (
135
- SELECT 1 FROM "users"
136
- WHERE "users"."id" = "posts"."author_id" AND (is_admin = 't')
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
- WHERE "comments"."post_id" = 1 AND (NOT EXISTS (
149
- SELECT 1 FROM "users"
150
- WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 't'
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
- WHERE (COALESCE((
163
- SELECT COUNT(*) FROM "comments"
164
- WHERE "comments"."post_id" = "posts"."id" AND "comments"."is_reported" = 't'
165
- ), 0) BETWEEN 5 AND 10)
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
- WHERE (EXISTS (
177
- SELECT 1 FROM "posts"
178
- WHERE "posts"."id" = "comments"."post_id" AND "posts"."author_id" = 1
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
- WHERE (EXISTS (
193
- SELECT 1 FROM "comments"
194
- WHERE "comments"."post_id" = "posts"."id" AND (EXISTS (
195
- SELECT 1 FROM "users"
196
- WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 't'
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
- WHERE (EXISTS (
210
- SELECT 1 FROM "comments"
211
- WHERE "comments"."post_id" = "posts"."id" AND (posts.author_id = comments.author_id)
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
- WHERE (EXISTS (
226
- SELECT 1 FROM "comments"
227
- WHERE "comments"."post_id" = "posts"."id" AND "comments"."is_reported" = 't' AND (EXISTS (
228
- SELECT 1 FROM "users"
229
- WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 't'
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
- WHERE "posts"."author_id" = 1 AND (EXISTS (
244
- SELECT 1 FROM "comments"
245
- WHERE "comments"."post_id" = "posts"."id" AND "comments"."is_reported" = 't'
246
- )) AND (EXISTS (
247
- SELECT 1 FROM "comments"
248
- WHERE "comments"."post_id" = "posts"."id" AND (EXISTS (
249
- SELECT 1 FROM "users"
250
- WHERE "users"."id" = "comments"."author_id" AND "users"."is_admin" = 't'
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.6.
43
- Works with SQLite3, PostgreSQL and MySQL. Untested with other RDBMS.
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/QueryMethods.html)
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
- If you prefer to see it in the code, [everything is in this file](https://github.com/MaxLap/activerecord_where_assoc/blob/master/lib/active_record_where_assoc/query_methods.rb)
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/QueryMethods#module-ActiveRecordWhereAssoc::QueryMethods-label-Options) to alter some behaviors. (rarely necessary)
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/QueryMethods.html#module-ActiveRecordWhereAssoc::QueryMethods-label-3Anever_alias_limit+option) option to avoid `#where_assoc_*` being overwritten by your scope and getting a weird exception / wrong result.
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/QueryMethods.html#module-ActiveRecordWhereAssoc::QueryMethods-label-3Aignore_limit+option) option. The behavior is less correct, but better than being unable to use the gem.
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 ActiveRecordWhereAssoc::QueryMethods#where_assoc_exists.
12
+ # A description for each can be found in RelationReturningMethods@Options.
13
13
  #
14
- # The only one that truly makes sense to change is :ignore_limit, when you are using MySQL, since
15
- # limit are never supported on it.
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/query_methods"
27
- require_relative "active_record_where_assoc/querying"
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::QueryMethods)
33
- ActiveRecord::Base.extend(ActiveRecordWhereAssoc::Querying)
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
- # Block used when nesting associations for a where_assoc_[not_]exists
12
- # Will apply the nested scope to the wrapping_scope with: where("EXISTS (SELECT... *nested_scope*)")
13
- # exists_prefix: raw sql prefix to the EXISTS, ex: 'NOT '
14
- NestWithExistsBlock = lambda do |wrapping_scope, nested_scopes, exists_prefix = ""|
15
- nested_scopes = [nested_scopes] unless nested_scopes.is_a?(Array)
16
- sql = nested_scopes.map { |ns| "EXISTS (#{ns.select('1').to_sql})" }.join(" OR ")
17
- sql = "(#{sql})" if nested_scopes.size > 1
18
- sql = sql.presence || "0=1"
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
- wrapping_scope.where(exists_prefix + sql)
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
- # Block used when nesting associations for a where_assoc_count
24
- # Will apply the nested scope to the wrapping_scope with: select("SUM(SELECT... *nested_scope*)")
25
- NestWithSumBlock = lambda do |wrapping_scope, nested_scopes|
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
- sql = nested_scopes.map { |ns| "SUM((#{ns.to_sql}))" }.join(" + ").presence || "0"
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 a new relation, which is the result of filtering base_relation
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 in query_methods.rb for usage details.
50
- def self.do_where_assoc_exists(base_relation, association_name, given_conditions, options, &block)
51
- nested_relations = relations_on_association(base_relation, association_name, given_conditions, options, block, NestWithExistsBlock)
52
- NestWithExistsBlock.call(base_relation, nested_relations)
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 a new relation, which is the result of filtering base_relation
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 #where_assoc_exists in query_methods.rb for usage details.
59
- def self.do_where_assoc_not_exists(base_relation, association_name, given_conditions, options, &block)
60
- nested_relations = relations_on_association(base_relation, association_name, given_conditions, options, block, NestWithExistsBlock)
61
- NestWithExistsBlock.call(base_relation, nested_relations, "NOT ")
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
- # Returns a new relation, which is the result of filtering base_relation
65
- # based on how many records for the specified association of the model exists.
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 #where_assoc_exists and #where_assoc_count in query_methods.rb for usage details.
68
- def self.do_where_assoc_count(base_relation, left_operand, operator, association_name, given_conditions, options, &block)
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(base_relation, association_name, given_conditions, options, deepest_scope_mod, NestWithSumBlock)
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
- right_sql = nested_relations.map { |nr| "COALESCE((#{nr.to_sql}), 0)" }.join(" + ").presence || "0"
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
- sql = sql_for_count_operator(left_operand, operator, right_sql)
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 only for polymorphic belongs_to
85
- # association_names_path: can be an array of association names or a single one
86
- def self.relations_on_association(base_relation, association_names_path, given_conditions, options, last_assoc_block, nest_assocs_block)
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
- association_names_path = Array.wrap(association_names_path)
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
- if association_names_path.size > 1
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 = relations_on_association(scope,
93
- association_names_path[1..-1],
94
- given_conditions,
95
- options,
96
- last_assoc_block,
97
- nest_assocs_block)
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(base_relation, association_names_path.first, nil, options, recursive_scope_block, nest_assocs_block)
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(base_relation, association_names_path.first, given_conditions, options, last_assoc_block, nest_assocs_block)
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(base_relation, association_name, given_conditions, options, last_assoc_block, nest_assocs_block)
110
- relation_klass = base_relation.klass
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, relation_klass, options)
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/QueryMethods.html#module-ActiveRecordWhereAssoc::QueryMethods-label-3Apoly_belongs_to+option"
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 ActiveRecordWhereAssoc::QueryMethods
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
- # You may also consider viewing the gem's README. It contains known issues and some tips.
22
- # You can view the {README on github}[https://github.com/MaxLap/activerecord_where_assoc/blob/master/README.md].
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
- # If you need extra convincing to try this gem, I have a whole document with the problems of
25
- # the other ways of doing this kind of filtering:
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 QueryMethods
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 ActiveRecordWhereAssoc::QueryMethods@Association
236
+ # See RelationReturningMethods@Association
225
237
  #
226
238
  # [condition]
227
239
  # Extra conditions the association must match <br>
228
- # See ActiveRecordWhereAssoc::QueryMethods@Condition
240
+ # See RelationReturningMethods@Condition
229
241
  #
230
242
  # [options]
231
243
  # Options to alter the generated query <br>
232
- # See ActiveRecordWhereAssoc::QueryMethods@Options
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 ActiveRecordWhereAssoc::QueryMethods@Block
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.do_where_assoc_exists(self, association_name, conditions, options, &block)
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 ActiveRecordWhereAssoc::QueryMethods@Association
282
+ # See RelationReturningMethods@Association
269
283
  #
270
284
  # [condition]
271
285
  # Extra conditions the association must not match <br>
272
- # See ActiveRecordWhereAssoc::QueryMethods@Condition
286
+ # See RelationReturningMethods@Condition
273
287
  #
274
288
  # [options]
275
289
  # Options to alter the generated query <br>
276
- # See ActiveRecordWhereAssoc::QueryMethods@Options
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 ActiveRecordWhereAssoc::QueryMethods@Block
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.do_where_assoc_not_exists(self, association_name, conditions, options, &block)
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 ActiveRecordWhereAssoc::QueryMethods@Association
367
+ # See RelationReturningMethods@Association
352
368
  #
353
369
  # [condition]
354
370
  # Extra conditions the association must match to count <br>
355
- # See ActiveRecordWhereAssoc::QueryMethods@Condition
371
+ # See RelationReturningMethods@Condition
356
372
  #
357
373
  # [options]
358
374
  # Options to alter the generated query <br>
359
- # See ActiveRecordWhereAssoc::QueryMethods@Options
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 ActiveRecordWhereAssoc::QueryMethods@Block
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.do_where_assoc_count(self, left_operand, operator, association_name, conditions, options, &block)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordWhereAssoc
4
- VERSION = "1.0.1".freeze
4
+ VERSION = "1.1.0".freeze
5
5
  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.1
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: 2019-10-16 00:00:00.000000000 Z
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/query_methods.rb
184
- - lib/active_record_where_assoc/querying.rb
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
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Needed for delegate
4
- require "active_support"
5
-
6
- module ActiveRecordWhereAssoc
7
- module Querying
8
- new_query_methods = QueryMethods.public_instance_methods
9
- delegate(*new_query_methods, to: :all)
10
- end
11
- end