activerecord_where_assoc 0.1.3 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,4 +3,7 @@
3
3
  module ActiveRecordWhereAssoc
4
4
  class MySQLDoesntSupportSubLimitError < StandardError
5
5
  end
6
+
7
+ class PolymorphicBelongsToWithoutClasses < StandardError
8
+ end
6
9
  end
@@ -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
@@ -0,0 +1,408 @@
1
+ # frozen_string_literal: true
2
+
3
+ # See RelationReturningMethods
4
+ module ActiveRecordWhereAssoc
5
+ # This module adds new variations of +#where+ to your Models/relations/associations/scopes.
6
+ # These variations check if an association has records, so you can check if a +Post+ has
7
+ # any +Comments+.
8
+ #
9
+ # These variations return a new relation (just like +#where+) so you can chain them with
10
+ # other scoping methods such as +#where+, +#order+, +#limit+, more of these variations, etc.
11
+ #
12
+ # The arguments common to all methods are documented here at the top.
13
+ #
14
+ # For brevity, the examples are all directly on models, such as User, Post, Comment, but
15
+ # the methods are available and behave the same on:
16
+ # * associations: <tt>my_user.posts.where_assoc_exists(:comments)</tt>
17
+ # * relations: <tt>Posts.where(serious: true).where_assoc_exists(:comments)</tt>
18
+ # * scopes: (On the Post model) <tt>scope :with_comments, -> { where_assoc_exists(:comments) }</tt>
19
+ # * models: <tt>Post.where_assoc_exists(:comments)</tt>
20
+ #
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
+ #
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:
35
+ # {alternatives' problems}[https://github.com/MaxLap/activerecord_where_assoc/blob/master/ALTERNATIVES_PROBLEMS.md].
36
+ #
37
+ # === Association
38
+ # The associations referred here are the links between your different models. They are your
39
+ # +#belongs_to+, +#has_many+, +#has_one+, +#has_and_belongs_to_many+.
40
+ #
41
+ # This gem is about getting records from your database if their associations match (or don't
42
+ # match) a certain condition (which by default is just to exist).
43
+ #
44
+ # Every method here has an *association_name* parameter. This is the association you want to
45
+ # check if records exists.
46
+ #
47
+ # # Posts with at least one comment
48
+ # Post.where_assoc_exists(:comments)
49
+ #
50
+ # # Posts with no comments
51
+ # Post.where_assoc_not_exists(:comments)
52
+ #
53
+ # If you want, you can pass an array of associations. They will be followed in order, just
54
+ # like a has_many :through would.
55
+ #
56
+ # # Posts which have at least one comment with a reply
57
+ # # In other words: Posts which have at least one reply reachable through his comments
58
+ # Post.where_assoc_exists([:comments, :replies])
59
+ #
60
+ # === Condition
61
+ # After the +association_name+ argument, you can pass conditions on your association to
62
+ # specify which of its records you care about. For example, you could only want Posts that
63
+ # have a comment marked as spam, so all you care about are comments marked as spam.
64
+ #
65
+ # Another way to look at this is that you are filtering your association (using a +#where+)
66
+ # and checking if a record of that association is still found, and you do this for each of you records.
67
+ #
68
+ # This +condition+ argument is passed directly to +#where+, so you can pass in the following:
69
+ #
70
+ # # Posts that have at least one comment considered as spam
71
+ # # Using a Hash
72
+ # Post.where_assoc_exists(:comments, is_spam: true)
73
+ #
74
+ # # Using a String
75
+ # Post.where_assoc_exists(:comments, "is_spam = true")
76
+ #
77
+ # # Using an Array (a string and its binds)
78
+ # Post.where_assoc_exists(:comments, ["is_spam = ?", true])
79
+ #
80
+ # If the condition is blank, it is ignored (just like +#where+ does).
81
+ #
82
+ # Note, if you specify multiple associations using an Array, the conditions will only be applied
83
+ # to the last association.
84
+ #
85
+ # # Users which have a post that has a comment marked as spam.
86
+ # # is_spam is only checked on the comment.
87
+ # User.where_assoc_exists([:posts, :comments], is_spam: true)
88
+ #
89
+ # If you want something else, you will need to use a block (see below) to nest multiple calls.
90
+ #
91
+ # # Users which have a post made in the last 5 days which has comments
92
+ # User.where_assoc_exists(:posts) {
93
+ # where("created_at > ?", 5.days.ago).where_assoc_exists(:comments)
94
+ # }
95
+ #
96
+ # === Block
97
+ # The block is used to add more complex conditions. The effect is the same as the condition
98
+ # parameter. You are specifying which records in the association you care about, but using
99
+ # a block lets you use any scoping methods, such as +#where+, +#joins+, nested
100
+ # +#where_assoc_*+, scopes on the model, etc.
101
+ #
102
+ # Note that using +#joins+ might lead to unexpected results when using #where_assoc_count,
103
+ # since if the joins adds rows, it will change the resulting count. It probably makes more
104
+ # sense to, again, use one of the +where_assoc_*+ methods (they can be nested).
105
+ #
106
+ # There are 2 ways of using the block for adding conditions to the association.
107
+ #
108
+ # [A block that receives one argument]
109
+ # The block receives a relation on the target association and return a relation with added
110
+ # filters or may return nil to do nothing.
111
+ #
112
+ # # These are all equivalent. Posts which have a comment marked as spam
113
+ # # Using a where for the added condition
114
+ # Post.where_assoc_exists(:comments) { |comments_scope| comments_scope.where(is_spam: true) }
115
+ #
116
+ # # Applying a scope of the relation
117
+ # Post.where_assoc_exists(:comments) { |comments_scope| comments_scope.spam_flagged }
118
+ #
119
+ # # Applying a scope of the relation, using the &:shortcut for procs
120
+ # Post.where_assoc_exists(:comments, &:spam_flagged)
121
+ #
122
+ # [A block that receives no argument]
123
+ # Instead of receiving the relation as argument, the relation is used as the "self" of
124
+ # the block. Everything else is identical to the block with one argument.
125
+ #
126
+ # # These are all equivalent. Posts which have a comment marked as spam
127
+ # # Using a where for the added condition
128
+ # Post.where_assoc_exists(:comments) { where(is_spam: true) }
129
+ #
130
+ # # Applying a scope of the relation
131
+ # Post.where_assoc_exists(:comments) { spam_flagged }
132
+ #
133
+ # The main reason to use a block with an argument instead of without one is when you need
134
+ # to call methods on the self outside of the block, such as:
135
+ #
136
+ # Post.where_assoc_exists(:comments) { |comments| comments.where(author_id: foo(:bar)) }
137
+ # Post.where_assoc_exists(:comments) { |comments| comments.where(author_id: self.foo(:bar)) }
138
+ # # In both cases, using the version without arguments would not work, since the #foo
139
+ # # would be called on the scope that was given to the block, instead of on the caller
140
+ # # of the #where_assoc_exists method.
141
+ #
142
+ # # THESE ARE WRONG!
143
+ # Post.where_assoc_exists(:comments) { where(author_id: foo(:bar)) }
144
+ # Post.where_assoc_exists(:comments) { where(author_id: self.foo(:bar)) }
145
+ # # THESE ARE WRONG!
146
+ #
147
+ # If both +condition+ and +block+ are given, the conditions are applied first, and then the block.
148
+ #
149
+ # === Options
150
+ # Some options are available to tweak how queries are generated. The default values of the options
151
+ # can be changed globally:
152
+ #
153
+ # # Somewhere in your setup code, such as an initializer in Rails
154
+ # ActiveRecordWhereAssoc.default_options[:ignore_limit] = true
155
+ #
156
+ # Or you can pass them as arguments after the +condition+ argument.
157
+ #
158
+ # Post.where_assoc_exists(:comments, "is_spam = TRUE", ignore_limit: true)
159
+ # # Because this is 2 consecutive hashes, must use the +{}+
160
+ # Post.where_assoc_exists(:comments, {is_spam: true}, ignore_limit: true)
161
+ #
162
+ # Note, if you don't need a condition, you must pass nil as condition to provide options:
163
+ # Post.where_assoc_exists(:comments, nil, ignore_limit: true)
164
+ #
165
+ # ===== :ignore_limit option
166
+ # When true, +#limit+ and +#offset+ that are set from default_scope, on associations, and from
167
+ # +#has_one+ are ignored. <br>
168
+ # Removing the limit from +#has_one+ makes them be treated like a +#has_many+.
169
+ #
170
+ # Main reasons to use ignore_limit: true
171
+ # * Needed for MySQL to be able to do anything with +#has_one+ associations because MySQL
172
+ # doesn't support sub-limit. <br>
173
+ # See {MySQL doesn't support limit}[https://github.com/MaxLap/activerecord_where_assoc#mysql-doesnt-support-sub-limit] <br>
174
+ # Note, this does mean the +#has_one+ will be treated as if it was a +#has_many+ for MySQL too.
175
+ # * You have a +#has_one+ association which you know can never have more than one record and are
176
+ # dealing with a heavy/slow query. The query used to deal with +#has_many+ is less complex, and
177
+ # may prove faster.
178
+ # * For this one special case, you want to check the other records that match your has_one
179
+ #
180
+ # ===== :never_alias_limit option
181
+ # When true, +#where_assoc_*+ will not use +#from+ to build relations that have +#limit+ or +#offset+ set
182
+ # on default_scope or on associations or for +#has_one+. <br>
183
+ # This allows changing the from as part of the conditions (such as for a scope)
184
+ #
185
+ # Main reasons to use this: you have to use +#from+ in the block of +#where_assoc_*+ method
186
+ # (ex: because a scope needs +#from+).
187
+ #
188
+ # Why this isn't the default:
189
+ # * From very few tests, the aliasing way seems to produce better plans.
190
+ # * Using aliasing produces a shorter query.
191
+ #
192
+ # ===== :poly_belongs_to option
193
+ # Specify what to do when a polymorphic belongs_to is encountered. Things are tricky because the query can
194
+ # end up searching in multiple Models, and just knowing which ones to look into can require an expensive query.
195
+ # It's also possible that you only want to search for those that match some specific Models, ignoring the other ones.
196
+ # [:pluck]
197
+ # Do a +#pluck+ in the column to detect to possible choices. This option can have a performance cost for big tables
198
+ # or when the query if done often, as the +#pluck+ will be executed each time.
199
+ # It is important to note that this happens when the query is prepared, so using this with methods that return SQL
200
+ # (such as SqlReturningMethods#assoc_exists_sql) will still execute a query even if you don't
201
+ # use the returned string.
202
+ # [model or array of models]
203
+ # Specify which models to search for. This avoids the performance cost of +#pluck+ and can allow to filter some
204
+ # of the choices out that don't interest you. <br>
205
+ # Note, these are not instances, it's actual models, ex: <code>[Post, Comment]</code>
206
+ # [a hash]
207
+ # The keys must be models (same behavior as an array of models). <br>
208
+ # The values are conditions to apply only for key's model.
209
+ # The conditions are either a proc (behaves like the block, but only for that model) or the same things +#where+
210
+ # can receive. (String, Hash, Array, nil). Ex:
211
+ # List.where_assoc_exists(:items, nil, poly_belongs_to: {Car => "color = 'blue'",
212
+ # Computer => proc { brand_new.where(core: 4) } })
213
+ # [:raise]
214
+ # (default) raise an exception when a polymorphic belongs_to is encountered.
215
+ module RelationReturningMethods
216
+ # :section: Basic methods
217
+
218
+ # Returns a new relation with a condition added (a +#where+) that checks if an association
219
+ # of the model exists. Extra conditions the associated model must match can also be specified.
220
+ #
221
+ # You could say this is a way of doing a +#select+ that uses associations of your model
222
+ # on the SQL side, but faster and more concise.
223
+ #
224
+ # Examples (with an equivalent ruby +#select+)
225
+ #
226
+ # # Posts that have comments
227
+ # Post.where_assoc_exists(:comments)
228
+ # Post.all.select { |post| post.comments.exists? }
229
+ #
230
+ # # Posts that have comments marked as spam
231
+ # Post.where_assoc_exists(:comments, is_spam: true)
232
+ # Post.select { |post| post.comments.any? {|comment| comment.is_spam } }
233
+ #
234
+ # # Posts that have comments that have replies
235
+ # Post.where_assoc_exists([:comments, :replies])
236
+ # Post.select { |post| post.comments.any? {|comment| comment.replies.exists? } }
237
+ #
238
+ # [association_name]
239
+ # The association that must exist <br>
240
+ # See RelationReturningMethods@Association
241
+ #
242
+ # [condition]
243
+ # Extra conditions the association must match <br>
244
+ # See RelationReturningMethods@Condition
245
+ #
246
+ # [options]
247
+ # Options to alter the generated query <br>
248
+ # See RelationReturningMethods@Options
249
+ #
250
+ # [&block]
251
+ # More complex conditions the associated record must match (can also use scopes of the association's model) <br>
252
+ # See RelationReturningMethods@Block
253
+ #
254
+ # You can get the SQL string of the condition using SqlReturningMethods#assoc_exists_sql.
255
+ def where_assoc_exists(association_name, conditions = nil, options = {}, &block)
256
+ sql = ActiveRecordWhereAssoc::CoreLogic.assoc_exists_sql(self.klass, association_name, conditions, options, &block)
257
+ where(sql)
258
+ end
259
+
260
+ # Returns a new relation with a condition added (a +#where+) that checks if an association
261
+ # of the model does not exist. Extra conditions the associated model that exists must not match
262
+ # can also be specified.
263
+ #
264
+ # This the exact opposite of what #where_assoc_exists does, so a #where_assoc_not_exists with
265
+ # the same arguments will keep every records that were rejected by the #where_assoc_exists.
266
+ #
267
+ # You could say this is a way of doing a +#reject+ that uses associations of your model
268
+ # on the SQL side, but faster and more concise.
269
+ #
270
+ # Examples (with an equivalent ruby +#reject+)
271
+ #
272
+ # # Posts that have no comments
273
+ # Post.where_assoc_not_exists(:comments)
274
+ # Post.all.reject { |post| post.comments.exists? }
275
+ #
276
+ # # Posts that don't have comments marked as spam (but might have unmarked comments)
277
+ # Post.where_assoc_not_exists(:comments, is_spam: true)
278
+ # Post.reject { |post| post.comments.any? {|comment| comment.is_spam } }
279
+ #
280
+ # # Posts that don't have comments that have replies (but can have comments that have no replies)
281
+ # Post.where_assoc_exists([:comments, :replies])
282
+ # Post.reject { |post| post.comments.any? {|comment| comment.replies.exists? } }
283
+ #
284
+ # [association_name]
285
+ # The association that must exist <br>
286
+ # See RelationReturningMethods@Association
287
+ #
288
+ # [condition]
289
+ # Extra conditions the association must not match <br>
290
+ # See RelationReturningMethods@Condition
291
+ #
292
+ # [options]
293
+ # Options to alter the generated query <br>
294
+ # See RelationReturningMethods@Options
295
+ #
296
+ # [&block]
297
+ # More complex conditions the associated record must match (can also use scopes of the association's model) <br>
298
+ # See RelationReturningMethods@Block
299
+ #
300
+ # You can get the SQL string of the condition using SqlReturningMethods#assoc_not_exists_sql.
301
+ def where_assoc_not_exists(association_name, conditions = nil, options = {}, &block)
302
+ sql = ActiveRecordWhereAssoc::CoreLogic.assoc_not_exists_sql(self.klass, association_name, conditions, options, &block)
303
+ where(sql)
304
+ end
305
+
306
+ # :section: Complex method
307
+
308
+ # Returns a new relation with a condition added (a +#where+) that checks how many records an association
309
+ # of the model has. Extra conditions the associated model must match can also be specified.
310
+ #
311
+ # This method is a generalization of #where_assoc_exists and #where_assoc_not_exists. It does the same
312
+ # thing, but can be more precise over how many records should exist (and match the extra conditions)
313
+ # To clarify, here are equivalent examples:
314
+ #
315
+ # Post.where_assoc_exists(:comments)
316
+ # Post.where_assoc_count(1, :<=, :comments)
317
+ #
318
+ # Post.where_assoc_not_exists(:comments)
319
+ # Post.where_assoc_count(0, :==, :comments)
320
+ #
321
+ # But these have no equivalent:
322
+ #
323
+ # # Posts with at least 5 comments
324
+ # Post.where_assoc_count(5, :<=, :comments)
325
+ #
326
+ # # Posts with less than 5 comments
327
+ # Post.where_assoc_count(5, :>, :comments)
328
+ #
329
+ # You could say this is a way of doing a +#select+ that +#count+ the associations of your model
330
+ # on the SQL side, but faster and more concise.
331
+ #
332
+ # Examples (with an equivalent ruby +#select+ and +#count+)
333
+ #
334
+ # # Posts with at least 5 comments
335
+ # Post.where_assoc_count(5, :<=, :comments)
336
+ # Post.all.select { |post| post.comments.count >= 5 }
337
+ #
338
+ # # Posts that have at least 5 comments marked as spam
339
+ # Post.where_assoc_count(5, :<=, :comments, is_spam: true)
340
+ # Post.all.select { |post| post.comments.where(is_spam: true).count >= 5 }
341
+ #
342
+ # # Posts that have at least 10 replies spread over their comments
343
+ # Post.where_assoc_count(10, :<=, [:comments, :replies])
344
+ # Post.select { |post| post.comments.sum { |comment| comment.replies.count } >= 5 }
345
+ #
346
+ # [left_operand]
347
+ # 1st argument, the left side of the comparison. <br>
348
+ # One of:
349
+ # * a number
350
+ # * a string of SQL to embed in the query
351
+ # * a range (operator must be :== or :!=), will use BETWEEN or NOT BETWEEN<br>
352
+ # supports infinite ranges and exclusive end
353
+ #
354
+ # # Posts with 5 to 10 comments
355
+ # Post.where_assoc_count(5..10, :==, :comments)
356
+ #
357
+ # # Posts with less than 5 or more than 10 comments
358
+ # Post.where_assoc_count(5..10, :!=, :comments)
359
+ #
360
+ # [operator]
361
+ # The operator to use, one of these symbols: <code> :< :<= :== :!= :>= :> </code>
362
+ #
363
+ # [association_name]
364
+ # The association that must have a certain number of occurrences <br>
365
+ # Note that if you use an array of association names, the number of the last association
366
+ # is what is counted.
367
+ #
368
+ # # Users which have received at least 5 comments total (can be spread on all of their posts)
369
+ # User.where_assoc_count(5, :<=, [:posts, :comments])
370
+ #
371
+ # See RelationReturningMethods@Association
372
+ #
373
+ # [condition]
374
+ # Extra conditions the association must match to count <br>
375
+ # See RelationReturningMethods@Condition
376
+ #
377
+ # [options]
378
+ # Options to alter the generated query <br>
379
+ # See RelationReturningMethods@Options
380
+ #
381
+ # [&block]
382
+ # More complex conditions the associated record must match (can also use scopes of the association's model) <br>
383
+ # See RelationReturningMethods@Block
384
+ #
385
+ # The order of the parameters may seem confusing. But you will get used to it. It helps
386
+ # to remember that the goal is to do:
387
+ # 5 < (SELECT COUNT(*) FROM ...)
388
+ # So the parameters are in the same order as in that query: number, operator, association.
389
+ #
390
+ # To be clear, when you use multiple associations in an array, the count you will be
391
+ # comparing against is the total number of records of that last association.
392
+ #
393
+ # # The users that have received at least 5 comments total on all of their posts
394
+ # # So this can be from one post that has 5 comments of from 5 posts with 1 comments
395
+ # User.where_assoc_count(5, :<=, [:posts, :comments])
396
+ #
397
+ # # The users that have at least 5 posts with at least one comments
398
+ # User.where_assoc_count(5, :<=, :posts) { where_assoc_exists(:comments) }
399
+ #
400
+ # You can get the SQL string of the condition using SqlReturningMethods#compare_assoc_count_sql.
401
+ # You can get the SQL string for only the counting using SqlReturningMethods#only_assoc_count_sql.
402
+ def where_assoc_count(left_operand, operator, association_name, conditions = nil, options = {}, &block)
403
+ sql = ActiveRecordWhereAssoc::CoreLogic.compare_assoc_count_sql(self.klass, left_operand, operator,
404
+ association_name, conditions, options, &block)
405
+ where(sql)
406
+ end
407
+ end
408
+ 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