activerecord_where_assoc 0.1.3 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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