activerecord_where_assoc 1.0.0 → 1.0.1

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
- SHA1:
3
- metadata.gz: c5feb6230bf9dd2b1e1b8d74443da251e7ae7fbf
4
- data.tar.gz: 1917b5b495c2808adf89737ab31676ca99fa5923
2
+ SHA256:
3
+ metadata.gz: 455c7e8523f81f043fe2933c78e54e3799f66f01bff81dd34d21185c3b268aaf
4
+ data.tar.gz: 1895e78079d3f9f06f532e01dcd7313a9c7fa27e3f38ec411359a1352fd755db
5
5
  SHA512:
6
- metadata.gz: 858c201216236ced5dbc8580874153319f11526849edb21b900302f59aabc7d99d4bd56e35a74c49ef5cd4fe07e8d0b0c4ee092d8fc8cefdcd40f0024dc371ef
7
- data.tar.gz: c2ae301a6ad1f8920d33608527db33241c6e6f2bb1f14151cfa8db100af0ad5b24a01debdc46efbbde1268fd4fa7f687b89dce09a147bc8c4f7a174743e8d75a
6
+ metadata.gz: fecba216f37489edaaf31945436228781fae1a102a336e93e3958b47305ca04493981b6c8291743cdfd54db236560a2c3027cae61c4988997f573368b3ad7770
7
+ data.tar.gz: d2011c3dbee23ab3244df38e2069a5bbac4ac8c062ed4028af3090bb91222f31f98132bc62e5db0804af34736310b9d1e763dc82b5cc99b1d231fb24c1553e9e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ # 1.0.1
2
+
3
+ * Fix broken urls in error messages
1
4
 
2
5
  # 1.0.0
3
6
 
data/README.md CHANGED
@@ -20,6 +20,8 @@ my_user.posts.where_assoc_count(5, :>=, :comments) { |comments| comments.not_spa
20
20
 
21
21
  These allow for powerful, chainable, clear and easy to reuse queries. (Great for scopes)
22
22
 
23
+ Here is an [introduction to this gem](INTRODUCTION.md).
24
+
23
25
  You avoid many [problems with the alternative options](ALTERNATIVES_PROBLEMS.md).
24
26
 
25
27
  Here are [many examples](EXAMPLES.md), including the generated SQL queries.
@@ -54,6 +56,12 @@ Or install it yourself as:
54
56
 
55
57
  $ gem install activerecord_where_assoc
56
58
 
59
+ ## Documentation
60
+
61
+ The [documentation is nicely structured](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/QueryMethods.html)
62
+
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)
64
+
57
65
  ## Usage
58
66
 
59
67
  The [documentation is nicely structured](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/QueryMethods.html)
@@ -95,7 +103,7 @@ where_assoc_count(left_operand, operator, association_name, conditions, options,
95
103
  Post.where_assoc_count(5, :<=, :comments, is_spam: true)
96
104
  ```
97
105
  * `where_assoc_count`'s additional arguments
98
- The order of the parameters of `#where_assoc_count` can be confusingof may seem confusing, but you will get used to it. To help remember: the goal is to do: `5 < (SELECT COUNT(*) FROM ...)`, the number is first, then operator, then the association and its conditions.
106
+ The order of the parameters of `#where_assoc_count` may seem confusing, but you will get used to it. It helps to remember: the goal is to do: `5 < (SELECT COUNT(*) FROM ...)`, the number is first, then operator, then the association and its conditions.
99
107
  * left_operand:
100
108
  * a number
101
109
  * a string of SQL to embed in the query
@@ -171,7 +179,7 @@ All the methods always chain nested associations using an `EXISTS` when they hav
171
179
 
172
180
  ### Using `#from` in scope
173
181
 
174
- If you want to use a scope / condition which uses `#from`, then you need to use the [:never_alias_limit](#never_alias_limit) option to avoid `#where_assoc_*` being overwritten by your scope and getting a weird exception / wrong result.
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.
175
183
 
176
184
  ## Known issues/limitations
177
185
 
@@ -180,7 +188,7 @@ On MySQL databases, it is not possible to use `has_one` associations and associa
180
188
 
181
189
  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.
182
190
 
183
- In order to work around this, you must use the [ignore_limit](#ignore_limit) option. The behavior is less correct, but better than being unable to use the gem.
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.
184
192
 
185
193
  ### has_* :through vs limit/offset
186
194
  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.
@@ -29,7 +29,6 @@ require_relative "active_record_where_assoc/querying"
29
29
  ActiveSupport.on_load(:active_record) do
30
30
  ActiveRecord.eager_load!
31
31
 
32
- # Need to use #send for the include to support Ruby 2.0
33
- ActiveRecord::Relation.send(:include, ActiveRecordWhereAssoc::QueryMethods)
32
+ ActiveRecord::Relation.include(ActiveRecordWhereAssoc::QueryMethods)
34
33
  ActiveRecord::Base.extend(ActiveRecordWhereAssoc::Querying)
35
34
  end
@@ -284,7 +284,7 @@ module ActiveRecordWhereAssoc
284
284
  end
285
285
  msg << "This is not supported by ActiveRecord when doing joins, but it is by WhereAssoc. However, "
286
286
  msg << "you must pass the :poly_belongs_to option to specify what to do in this case.\n"
287
- msg << "See https://github.com/MaxLap/activerecord_where_assoc#poly_belongs_to"
287
+ msg << "See https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/QueryMethods.html#module-ActiveRecordWhereAssoc::QueryMethods-label-3Apoly_belongs_to+option"
288
288
  raise ActiveRecordWhereAssoc::PolymorphicBelongsToWithoutClasses, msg
289
289
  else
290
290
  if on_poly_belongs_to.is_a?(Class) && on_poly_belongs_to < ActiveRecord::Base
@@ -315,7 +315,7 @@ module ActiveRecordWhereAssoc
315
315
  msg = String.new
316
316
  msg << "Associations and default_scopes with a limit or offset are not supported for MySQL (this includes has_many). "
317
317
  msg << "Use ignore_limit: true to ignore both limit and offset, and treat has_one like has_many. "
318
- msg << "See https://github.com/MaxLap/activerecord_where_assoc/tree/ignore_limits#mysql-doesnt-support-sub-limit for details."
318
+ msg << "See https://github.com/MaxLap/activerecord_where_assoc#mysql-doesnt-support-sub-limit for details."
319
319
  raise MySQLDoesntSupportSubLimitError, msg
320
320
  end
321
321
 
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "active_record_compat"
4
- require_relative "exceptions"
5
-
6
3
  # See ActiveRecordWhereAssoc::QueryMethods
7
4
  module ActiveRecordWhereAssoc
8
5
  # This module adds new variations of +#where+ to your Models/relations/associations/scopes.
@@ -134,6 +131,8 @@ module ActiveRecordWhereAssoc
134
131
  # Post.where_assoc_exists(:comments) { where(author_id: self.foo(:bar)) }
135
132
  # # THESE ARE WRONG!
136
133
  #
134
+ # If both +condition+ and +block+ are given, the conditions are applied first, and then the block.
135
+ #
137
136
  # === Options
138
137
  # Some options are available to tweak how queries are generated. The default values of the options
139
138
  # can be changed globally:
@@ -150,53 +149,53 @@ module ActiveRecordWhereAssoc
150
149
  # Note, if you don't need a condition, you must pass nil as condition to provide options:
151
150
  # Post.where_assoc_exists(:comments, nil, ignore_limit: true)
152
151
  #
153
- # [ignore_limit]
154
- # When true, +#limit+ and +#offset+ that are set from default_scope, on associations, and from
155
- # +#has_one+ are ignored. <br>
156
- # Removing the limit from +#has_one+ makes them be treated like a +#has_many+.
157
- #
158
- # Main reasons to use ignore_limit: true
159
- # * Needed for MySQL to be able to do anything with +#has_one+ associations because MySQL
160
- # doesn't support sub-limit. <br>
161
- # See {MySQL doesn't support limit}[https://github.com/MaxLap/activerecord_where_assoc#mysql-doesnt-support-sub-limit] <br>
162
- # Note, this does mean the +#has_one+ will be treated as if it was a +#has_many+ for MySQL too.
163
- # * You have a +#has_one+ association which you know can never have more than one record and are
164
- # dealing with a heavy/slow query. The query used to deal with +#has_many+ is less complex, and
165
- # may prove faster.
166
- # * For this one special case, you want to check the other records that match your has_one
167
- #
168
- # [never_alias_limit]
169
- # When true, +#where_assoc_*+ will not use +#from+ to build relations that have +#limit+ or +#offset+ set
170
- # on default_scope or on associations or for +#has_one+. <br>
171
- # This allows changing the from as part of the conditions (such as for a scope)
172
- #
173
- # Main reasons to use this: you have to use +#from+ in the block of +#where_assoc_*+ method
174
- # (ex: because a scope needs +#from+).
175
- #
176
- # Why this isn't the default:
177
- # * From very few tests, the aliasing way seems to produce better plans.
178
- # * Using aliasing produces a shorter query.
179
- #
180
- # [poly_belongs_to]
181
- # Specify what to do when a polymorphic belongs_to is encountered. Things are tricky because the query can
182
- # end up searching in multiple Models, and just knowing which ones to look into can require an expensive query.
183
- # It's also possible that you only want to search for those that match some specific Models, ignoring the other ones.
184
- # [:pluck]
185
- # Do a +#pluck+ in the column to detect to possible choices. This option can have a performance cost for big tables
186
- # or when the query if done often, as the +#pluck+ will be executed each time
187
- # [model or array of models]
188
- # Specify which models to search for. This avoids the performance cost of +#pluck+ and can allow to filter some
189
- # of the choices out that don't interest you. <br>
190
- # Note, these are not instances, it's actual models, ex: <code>[Post, Comment]</code>
191
- # [a hash]
192
- # The keys must be models (same behavior as an array of models). <br>
193
- # The values are conditions to apply only for key's model.
194
- # The conditions are either a proc (behaves like the block, but only for that model) or the same things +#where+
195
- # can receive. (String, Hash, Array, nil). Ex:
196
- # List.where_assoc_exists(:items, nil, poly_belongs_to: {Car => "color = 'blue'",
197
- # Computer => proc { brand_new.where(core: 4) } })
198
- # [:raise]
199
- # (default) raise an exception when a polymorphic belongs_to is encountered.
152
+ # ===== :ignore_limit option
153
+ # When true, +#limit+ and +#offset+ that are set from default_scope, on associations, and from
154
+ # +#has_one+ are ignored. <br>
155
+ # Removing the limit from +#has_one+ makes them be treated like a +#has_many+.
156
+ #
157
+ # Main reasons to use ignore_limit: true
158
+ # * Needed for MySQL to be able to do anything with +#has_one+ associations because MySQL
159
+ # doesn't support sub-limit. <br>
160
+ # See {MySQL doesn't support limit}[https://github.com/MaxLap/activerecord_where_assoc#mysql-doesnt-support-sub-limit] <br>
161
+ # Note, this does mean the +#has_one+ will be treated as if it was a +#has_many+ for MySQL too.
162
+ # * You have a +#has_one+ association which you know can never have more than one record and are
163
+ # dealing with a heavy/slow query. The query used to deal with +#has_many+ is less complex, and
164
+ # may prove faster.
165
+ # * For this one special case, you want to check the other records that match your has_one
166
+ #
167
+ # ===== :never_alias_limit option
168
+ # When true, +#where_assoc_*+ will not use +#from+ to build relations that have +#limit+ or +#offset+ set
169
+ # on default_scope or on associations or for +#has_one+. <br>
170
+ # This allows changing the from as part of the conditions (such as for a scope)
171
+ #
172
+ # Main reasons to use this: you have to use +#from+ in the block of +#where_assoc_*+ method
173
+ # (ex: because a scope needs +#from+).
174
+ #
175
+ # Why this isn't the default:
176
+ # * From very few tests, the aliasing way seems to produce better plans.
177
+ # * Using aliasing produces a shorter query.
178
+ #
179
+ # ===== :poly_belongs_to option
180
+ # Specify what to do when a polymorphic belongs_to is encountered. Things are tricky because the query can
181
+ # end up searching in multiple Models, and just knowing which ones to look into can require an expensive query.
182
+ # It's also possible that you only want to search for those that match some specific Models, ignoring the other ones.
183
+ # [:pluck]
184
+ # 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
186
+ # [model or array of models]
187
+ # Specify which models to search for. This avoids the performance cost of +#pluck+ and can allow to filter some
188
+ # of the choices out that don't interest you. <br>
189
+ # Note, these are not instances, it's actual models, ex: <code>[Post, Comment]</code>
190
+ # [a hash]
191
+ # The keys must be models (same behavior as an array of models). <br>
192
+ # The values are conditions to apply only for key's model.
193
+ # The conditions are either a proc (behaves like the block, but only for that model) or the same things +#where+
194
+ # can receive. (String, Hash, Array, nil). Ex:
195
+ # List.where_assoc_exists(:items, nil, poly_belongs_to: {Car => "color = 'blue'",
196
+ # Computer => proc { brand_new.where(core: 4) } })
197
+ # [:raise]
198
+ # (default) raise an exception when a polymorphic belongs_to is encountered.
200
199
  module QueryMethods
201
200
  # :section: Basic methods
202
201
 
@@ -342,7 +341,13 @@ module ActiveRecordWhereAssoc
342
341
  # The operator to use, one of these symbols: <code> :< :<= :== :!= :>= :> </code>
343
342
  #
344
343
  # [association_name]
345
- # The association that must exist <br>
344
+ # The association that must have a certain number of occurrences <br>
345
+ # Note that if you use an array of association names, the number of the last association
346
+ # is what is counted.
347
+ #
348
+ # # Users which have received at least 5 comments total (can be spread on all of their posts)
349
+ # User.where_assoc_count(5, :<=, [:posts, :comments])
350
+ #
346
351
  # See ActiveRecordWhereAssoc::QueryMethods@Association
347
352
  #
348
353
  # [condition]
@@ -357,8 +362,8 @@ module ActiveRecordWhereAssoc
357
362
  # More complex conditions the associated record must match (can also use scopes of the association's model) <br>
358
363
  # See ActiveRecordWhereAssoc::QueryMethods@Block
359
364
  #
360
- # The order of the parameters may seem confusing. But you will get used to it. To help
361
- # remember the order of the parameters, remember that the goal is to do:
365
+ # The order of the parameters may seem confusing. But you will get used to it. It helps
366
+ # to remember that the goal is to do:
362
367
  # 5 < (SELECT COUNT(*) FROM ...)
363
368
  # So the parameters are in the same order as in that query: number, operator, association.
364
369
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordWhereAssoc
4
- VERSION = "1.0.0".freeze
4
+ VERSION = "1.0.1".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.0
4
+ version: 1.0.1
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-05-10 00:00:00.000000000 Z
11
+ date: 2019-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -172,7 +172,6 @@ executables: []
172
172
  extensions: []
173
173
  extra_rdoc_files: []
174
174
  files:
175
- - ALTERNATIVES_PROBLEMS.md
176
175
  - CHANGELOG.md
177
176
  - EXAMPLES.md
178
177
  - LICENSE.txt
@@ -204,8 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
203
  - !ruby/object:Gem::Version
205
204
  version: '0'
206
205
  requirements: []
207
- rubyforge_project:
208
- rubygems_version: 2.6.11
206
+ rubygems_version: 3.0.3
209
207
  signing_key:
210
208
  specification_version: 4
211
209
  summary: Make ActiveRecord do conditions on your associations
@@ -1,231 +0,0 @@
1
- There are multiple ways of achieving results similar to what this gems does using either only built-in ActiveRecord functionalities or other gems.
2
-
3
- This is a list of some of those alternatives, explaining what issues they have or reasons to prefer this gem over them.
4
-
5
-
6
- ## Too long; didn't read
7
-
8
- **Use this gem, you will avoid problems and save time**
9
-
10
- * No more having to choose, case by case, which way has the less problems.
11
- Just use `#where_assoc_*` each time and avoid every problems.
12
- * Need less raw SQL, which means less code, more clarity and less maintenance.
13
- * Generates a single `#where`. No weird side-effects things like `#eager_load` or `#join`
14
- This makes well-behaved scopes, you can even have multiple conditions on the same association
15
- * Handles recursive associations correctly.
16
- * Handles has_one correctly (Except [MySQL has a limitation](README.md#mysql-doesnt-support-sub-limit)).
17
- * Handles polymorphic belongs_to
18
-
19
- ## Short version
20
-
21
- Summary of the problems of the alternatives that `activerecord_where_assoc` solves. The following sections go in more details.
22
-
23
- * every alternatives (except raw SQL):
24
- * treat `has_one` like a `has_many`.
25
- * can't handle recursive associations. (ex: parent/children)
26
- * no simple way of checking for more complex counts. (such as `less than 5`)
27
- * `joins` / `includes`:
28
- * doing `not exists` with conditions requires a `LEFT JOIN` with the conditions as part of the `ON`, which requires raw SQL.
29
- * checking for 2 sets of conditions on different records of the same association won't work. (so your scopes can be incompatible)
30
- * can't be used with Rails 5's `or` unless both sides do the same `joins` / `includes` / `eager_load`.
31
- * Doesn't work for polymorphic belongs_to.
32
- * `joins`:
33
- * `has_many` may return duplicate records.
34
- * using `uniq` / `distinct` to solve duplicate rows is an unexpected side-effect when this is in a scope.
35
- * `includes`:
36
- * triggers eagerloading, which makes your `scope` have unexpected bad performances if it's not necessary.
37
- * when using a condition, the eagerloaded records are also filtered, which is very bug-prone when in a scope.
38
- * raw SQL:
39
- * verbose, less clear on the goal of the queries (you don't even name the association the query is about).
40
- * need to repeat conditions from the association / default_scope.
41
- * `where_exists` gem:
42
- * can't use scopes of the association's model.
43
- * can't go deeper than one level of association.
44
-
45
- ## Common problems to most alternatives
46
-
47
- These are problems that affect most alternatives. Details are written in this section and just referred to by a one liner when they apply to an alternative.
48
-
49
- ### Treating has_one like has_many
50
-
51
- Every alternative treats a has_one just like a has_many. So if any of the records (instead of only the first) matches your condition, you will get a match.
52
-
53
- And example to clarify:
54
-
55
- ```ruby
56
- class Person < ActiveRecord::Base
57
- has_many :addresses
58
- has_one :current_address, -> { order("effective_date DESC") }, class_name: 'Address'
59
- end
60
-
61
- # This correctly matches only those whose current_address is in Montreal
62
- Person.where_assoc_exists(:current_address, city: 'Montreal')
63
-
64
- # Every alternatives (except raw SQL):
65
- # Matches those that have had an address in Montreal, no matter when
66
- Person.where_assoc_exists(:addresses, city: 'Montreal')
67
- ```
68
-
69
- The general version of this problem is the handling of `limit` and `offset` on associations and in default_scopes. where_assoc_exists handle those correctly and only checks the records that match the limit and the offset.
70
-
71
- Note: [MySQL has a limitation](README.md#mysql-doesnt-support-sub-limit), this makes handling has_one correctly not possible with MySQL.
72
-
73
- ### Raw SQL joins or sub-selects
74
-
75
- Having to write the joins and conditions in raw SQL is more painful and more error prone than having a method do it for you. It hides the important details of what you are doing in a lot of verbosity.
76
-
77
- If there are conditions set on either the association or a default_scope of the model, then you must rewrite those conditions in your manual joins and your manual sub-selects. Worst, if you add/change those conditions on the association / default_scope, then you must find every raw SQL that apply and do the same operation.
78
-
79
- ```ruby
80
- class Post < ActiveRecord::Base
81
- # Any raw SQL doing a join or sub-select on public_comments, if it want to be representative,
82
- # must repeat "public = true".
83
- has_many :public_comments, -> { where(public: true) }, class_name: 'Comment'
84
- end
85
-
86
- class Comment < ActiveRecord::Base
87
- # Any raw SQL doing a join or sub-select to this model, if it want to be representative,
88
- # must repeat "deleted_at IS NULL".
89
- default_scope -> { where(deleted_at: nil) }
90
- end
91
- ```
92
-
93
- All of this is avoided by where_assoc_* methods.
94
-
95
- ### Unable to handle recursive associations
96
-
97
- When you have recursive associations such as parent/children, you must compare multiple rows of the same table. To do this, you have no choice but to write your own raw SQL to, at the very least, do a SQL join with an alias.
98
-
99
- This brings us back to the [raw SQL joins](#raw-sql-joins-or-sub-selects) problem.
100
-
101
- `where_assoc_*` methods handle this seemlessly.
102
-
103
- ### Unable to handle polymorphic belongs_to
104
-
105
- When you have a polymorphic belongs_to, you can't use `joins` or `includes` in order to do queries on it. You have to use manual SQL ([raw SQL joins](#raw-sql-joins-or-sub-selects)) or a gem that provides the feature, such as `activerecord_where_assoc`.
106
-
107
- ## ActiveRecord only
108
-
109
- Those are the common ways given in stack overflow answers.
110
-
111
- ### Using `joins` and `where`
112
-
113
- ```ruby
114
- Post.where_assoc_exists(:comments, is_spam: true)
115
- Post.joins(:comments).where(comments: {is_spam: true})
116
- ```
117
-
118
- * If the association maps to multiple records (such as with a has_many), then the the relation will return one record for each matching association record. In this example, you would get the same post twice if it has 2 comments that are marked as spam.
119
- Using `uniq` can solve this issue, but if you do that in a scope, then that scope unexpectedly adds a DISTINCT to your query, which can lead to unexpected results if you actually wanted duplicates for a different reason.
120
-
121
- * Doing the opposite is a lot more complicated, as seen below. You have to include your conditions directly in the join and use a LEFT JOIN, this means writing the whole thing in raw SQL, and then you must check for the id of the association to be empty.
122
-
123
- ```ruby
124
- Post.where_assoc_not_exists(:comments, is_spam: true)
125
- Post.joins("LEFT JOIN comments ON posts.id = comments.post_id AND comments.is_spam = true").where(comments: {id: nil})
126
- ```
127
-
128
- Writing a raw join like that has yet more problems: [raw SQL joins](#raw-sql-joins-or-sub-selects)
129
-
130
- * If you want to have another condition referring to the same association (or just the same table), then you need to write out the SQL for the second join using an alias. Therefore, your scopes are not even compatible unless each of them has a join with a unique alias.
131
-
132
- ```ruby
133
- # We want to be able to match either different or the same records
134
- Post.where_assoc_exists(:comments, is_spam: true)
135
- .where_assoc_exists(:comments, is_reported: true)
136
-
137
- # Please don't ever do this, this just shows how painful it would be
138
- # If you reach the need to do this but won't use where_assoc_exists,
139
- # go for a regular #where("EXISTS( SELECT ...)")
140
- Post.joins(:comments).where(comments: {is_spam: true})
141
- .joins("JOIN comments comments_for_reported ON posts.id = comments_for_reported.post_id")
142
- .where(comments_for_reported: {is_reported: true})
143
- ```
144
-
145
- * Cannot be used with Rails 5's `or` unless both side do the same `joins`.
146
- * [Treats has_one like a has_many](#treating-has_one-like-has_many)
147
- * [Can't handle recursive associations](#unable-to-handle-recursive-associations)
148
- * [Can't handle polymorphic belongs_to](#unable-to-handle-polymorphic-belongs_to)
149
-
150
- ### Using `includes` (or `eager_load`) and `where`
151
-
152
- This solution is similar to the `joins` one above, but avoids the need for `uniq`. Every other problems of the `joins` remain. You also add other potential issues.
153
-
154
- ```ruby
155
- Post.where_assoc_exists(:comments, is_spam: true)
156
- Post.eager_load(:comments).where(comments: {is_spam: true})
157
- ```
158
-
159
- * You are triggering the loading of potentially lots of records that you might not need. You don't expect a scope like `have_reported_comments` to trigger eager loading. This is a performance degradation.
160
-
161
- * The eager loaded records of the association are actually also filtered by the conditions. All of the posts returned will only have the comments that are spam.
162
- This means if you iterate on `Post.have_reported_comments` to display each of the comments of the posts that have at least one reported comment, you are actually only going to display the reported comments. This may be what you wanted to do, but it clearly isn't intuitive.
163
-
164
- * Cannot be used with Rails 5's `or` unless both side do the same `includes` or `eager_load`.
165
-
166
- * [Treats has_one like a has_many](#treating-has_one-like-has_many)
167
- * [Can't handle recursive associations](#unable-to-handle-recursive-associations)
168
- * [Can't handle polymorphic belongs_to](#unable-to-handle-polymorphic-belongs_to)
169
-
170
- * Simply cannot be used for complex cases.
171
-
172
- Note: using `includes` (or `eager_load`) already does a LEFT JOIN, so it is pretty easy to do a "not exists", but only if you don't need any condition on the association (which would normally need to be in the JOIN clause):
173
-
174
- ```ruby
175
- Post.where_assoc_exists(:comments)
176
- Post.eager_load(:comments).where(comments: {id: nil})
177
- ```
178
-
179
- ### Using `where("EXISTS( SELECT... )")`
180
-
181
- This is what is gem does behind the scene, but doing it manually can lead to troubles:
182
-
183
- * Problems with writing [raw SQL sub-selects](#raw-sql-joins-or-sub-selects)
184
-
185
- * Unless you do a quite complex nested sub-selects, you will [treat has_one like a has_many](#treating-has_one-like-has_many)
186
-
187
-
188
- ## Gems
189
-
190
- ### where_exists
191
-
192
- https://github.com/EugZol/where_exists
193
-
194
- An interesting gem that also does `EXISTS (SELECT ... )` behind the scene. Solves most issues from ActiveRecord only alternatives, but appears less powerful than where_assoc_exists.
195
-
196
- * where_exists supports polymorphic belongs_to only by always doing a `pluck` everytime. In some situation could be a slow query if there is a lots of rows to scan. where_assoc also allows directly specifying the classes manually, avoiding the pluck and possibly filtering the choices.
197
-
198
- * Unable to use scopes of the association's model.
199
- ```ruby
200
- # There is no equivalent for this (admins is a scope on User)
201
- Comment.where_assoc_exists(:author, &:admins)
202
- ```
203
-
204
- * Cannot use a block for more complex conditions
205
- ```ruby
206
- # There is no equivalent for this
207
- Comment.where_assoc_exists(:author) { admins.where("created_at <= ?", 1.month.ago) }
208
- ```
209
-
210
- * Unable to dig deeper in the associations
211
- Note: it does follow :through associations so doing a custom associations for your need can be a workaround.
212
-
213
- ```ruby
214
- # There is no equivalent for this (Users that have posts with at least a comments)
215
- User.where_assoc_exists([:posts, :comments])
216
- ```
217
-
218
- * Has no equivalent to `where_assoc_count`
219
- ```ruby
220
- # There is no equivalent for this (posts with more than 5 comments)
221
- Post.where_assoc_count(:comments, :>, 5)
222
- ```
223
-
224
- * [Treats has_one like a has_many](#treating-has_one-like-has_many)
225
-
226
- * [Can't handle recursive associations](#unable-to-handle-recursive-associations)
227
-
228
- * `where_exists` is shorter than `where_assoc_exists`, but it is also less obvious about what it does.
229
- In any case, it is trivial to alias one name to the other one.
230
-
231
- * where_exists supports Rails 4.2 and up, while where_assoc supports Rails 4.1 and up.