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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/EXAMPLES.md +150 -67
- data/README.md +107 -129
- data/lib/active_record_where_assoc.rb +17 -6
- data/lib/active_record_where_assoc/active_record_compat.rb +42 -10
- data/lib/active_record_where_assoc/core_logic.rb +265 -130
- data/lib/active_record_where_assoc/exceptions.rb +3 -0
- data/lib/active_record_where_assoc/relation_returning_delegates.rb +12 -0
- data/lib/active_record_where_assoc/relation_returning_methods.rb +408 -0
- data/lib/active_record_where_assoc/sql_returning_methods.rb +74 -0
- data/lib/active_record_where_assoc/version.rb +1 -1
- metadata +10 -25
- data/ALTERNATIVES_PROBLEMS.md +0 -221
- data/lib/active_record_where_assoc/query_methods.rb +0 -180
- data/lib/active_record_where_assoc/querying.rb +0 -11
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:
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maxime Handfield Lapointe
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -28,14 +28,14 @@ dependencies:
|
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '1.15'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.15'
|
41
41
|
- !ruby/object:Gem::Dependency
|
@@ -68,32 +68,18 @@ dependencies:
|
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rake
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '10.0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '10.0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: coveralls
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
86
72
|
requirements:
|
87
73
|
- - ">="
|
88
74
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
75
|
+
version: '10.0'
|
90
76
|
type: :development
|
91
77
|
prerelease: false
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
93
79
|
requirements:
|
94
80
|
- - ">="
|
95
81
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
82
|
+
version: '10.0'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
84
|
name: deep-cover
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -172,7 +158,6 @@ executables: []
|
|
172
158
|
extensions: []
|
173
159
|
extra_rdoc_files: []
|
174
160
|
files:
|
175
|
-
- ALTERNATIVES_PROBLEMS.md
|
176
161
|
- CHANGELOG.md
|
177
162
|
- EXAMPLES.md
|
178
163
|
- LICENSE.txt
|
@@ -181,8 +166,9 @@ files:
|
|
181
166
|
- lib/active_record_where_assoc/active_record_compat.rb
|
182
167
|
- lib/active_record_where_assoc/core_logic.rb
|
183
168
|
- lib/active_record_where_assoc/exceptions.rb
|
184
|
-
- lib/active_record_where_assoc/
|
185
|
-
- lib/active_record_where_assoc/
|
169
|
+
- lib/active_record_where_assoc/relation_returning_delegates.rb
|
170
|
+
- lib/active_record_where_assoc/relation_returning_methods.rb
|
171
|
+
- lib/active_record_where_assoc/sql_returning_methods.rb
|
186
172
|
- lib/active_record_where_assoc/version.rb
|
187
173
|
- lib/activerecord_where_assoc.rb
|
188
174
|
homepage: https://github.com/MaxLap/activerecord_where_assoc
|
@@ -204,8 +190,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
204
190
|
- !ruby/object:Gem::Version
|
205
191
|
version: '0'
|
206
192
|
requirements: []
|
207
|
-
|
208
|
-
rubygems_version: 2.7.6
|
193
|
+
rubygems_version: 3.0.3
|
209
194
|
signing_key:
|
210
195
|
specification_version: 4
|
211
196
|
summary: Make ActiveRecord do conditions on your associations
|
data/ALTERNATIVES_PROBLEMS.md
DELETED
@@ -1,221 +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
|
-
* Allows powerful scopes without traps.
|
14
|
-
* Handles recursive associations correctly.
|
15
|
-
* Handles has_one correctly.
|
16
|
-
|
17
|
-
## Short version
|
18
|
-
|
19
|
-
Summary of the problems of the alternatives that `activerecord_where_assoc` solves. The following sections go in more details.
|
20
|
-
|
21
|
-
* every alternatives (except raw SQL):
|
22
|
-
* treat `has_one` like a `has_many`.
|
23
|
-
* can't handle recursive associations. (ex: parent/children)
|
24
|
-
* no simple way of checking for more complex counts. (such as `less than 5`)
|
25
|
-
* `joins` / `includes`:
|
26
|
-
* doing `not exists` with conditions requires a `LEFT JOIN` with the conditions as part of the `ON`, which requires raw SQL.
|
27
|
-
* checking for 2 sets of conditions on different records of the same association won't work. (so your scopes can be incompatible)
|
28
|
-
* can't be used with Rails 5's `or` unless both sides do the same `joins` / `includes` / `eager_load`.
|
29
|
-
* `joins`:
|
30
|
-
* `has_many` may return duplicate records.
|
31
|
-
* using `uniq` / `distinct` to solve duplicate rows is an unexpected side-effect when this is in a scope.
|
32
|
-
* `includes`:
|
33
|
-
* triggers eagerloading, which makes your `scope` have unexpected bad performances if it's not necessary.
|
34
|
-
* when using a condition, the eagerloaded records are also filtered, which is very bug-prone when in a scope.
|
35
|
-
* raw SQL:
|
36
|
-
* verbose, less clear on the goal of the queries (you don't even name the association the query is about).
|
37
|
-
* need to repeat conditions from the association / default_scope.
|
38
|
-
* `where_exists` gem:
|
39
|
-
* can't use scopes of the association's model.
|
40
|
-
* can't go deeper than one level of association.
|
41
|
-
|
42
|
-
## Common problems to most alternatives
|
43
|
-
|
44
|
-
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.
|
45
|
-
|
46
|
-
### Treating has_one like has_many
|
47
|
-
|
48
|
-
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.
|
49
|
-
|
50
|
-
And example to clarify:
|
51
|
-
|
52
|
-
```ruby
|
53
|
-
class Person < ActiveRecord::Base
|
54
|
-
has_many :addresses
|
55
|
-
has_one :current_address, -> { order("effective_date DESC") }, class_name: 'Address'
|
56
|
-
end
|
57
|
-
|
58
|
-
# This correctly matches only those whose current_address is in Montreal
|
59
|
-
Person.where_assoc_exists(:current_address, city: 'Montreal')
|
60
|
-
|
61
|
-
# Every alternatives (except raw SQL):
|
62
|
-
# Matches those that have had an address in Montreal, no matter when
|
63
|
-
Person.where_assoc_exists(:addresses, city: 'Montreal')
|
64
|
-
```
|
65
|
-
|
66
|
-
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.
|
67
|
-
|
68
|
-
### Raw SQL joins or sub-selects
|
69
|
-
|
70
|
-
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.
|
71
|
-
|
72
|
-
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.
|
73
|
-
|
74
|
-
```ruby
|
75
|
-
class Post < ActiveRecord::Base
|
76
|
-
# Any raw SQL doing a join or sub-select on public_comments, if it want to be representative,
|
77
|
-
# must repeat "public = true".
|
78
|
-
has_many :public_comments, -> { where(public: true) }, class_name: 'Comment'
|
79
|
-
end
|
80
|
-
|
81
|
-
class Comment < ActiveRecord::Base
|
82
|
-
# Any raw SQL doing a join or sub-select to this model, if it want to be representative,
|
83
|
-
# must repeat "deleted_at IS NULL".
|
84
|
-
default_scope -> { where(deleted_at: nil) }
|
85
|
-
end
|
86
|
-
```
|
87
|
-
|
88
|
-
All of this is avoided by where_assoc_* methods.
|
89
|
-
|
90
|
-
### Unable to handle recursive associations
|
91
|
-
|
92
|
-
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.
|
93
|
-
|
94
|
-
This brings us back to the [raw SQL joins](#raw-sql-joins-or-sub-selects) problem.
|
95
|
-
|
96
|
-
`where_assoc_*` methods handle this seemlessly.
|
97
|
-
|
98
|
-
## ActiveRecord only
|
99
|
-
|
100
|
-
Those are the common ways given in stack overflow answers.
|
101
|
-
|
102
|
-
### Using `joins` and `where`
|
103
|
-
|
104
|
-
```ruby
|
105
|
-
Post.where_assoc_exists(:comments, is_spam: true)
|
106
|
-
Post.joins(:comments).where(comments: {is_spam: true})
|
107
|
-
```
|
108
|
-
|
109
|
-
* 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.
|
110
|
-
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.
|
111
|
-
|
112
|
-
* 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.
|
113
|
-
|
114
|
-
```ruby
|
115
|
-
Post.where_assoc_not_exists(:comments, is_spam: true)
|
116
|
-
Post.joins("LEFT JOIN comments ON posts.id = comments.post_id AND comments.is_spam = true").where(comments: {id: nil})
|
117
|
-
```
|
118
|
-
|
119
|
-
Writing a raw join like that has yet more problems: [raw SQL joins](#raw-sql-joins-or-sub-selects)
|
120
|
-
|
121
|
-
* 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.
|
122
|
-
|
123
|
-
```ruby
|
124
|
-
# We want to be able to match either different or the same records
|
125
|
-
Post.where_assoc_exists(:comments, is_spam: true)
|
126
|
-
.where_assoc_exists(:comments, is_reported: true)
|
127
|
-
|
128
|
-
# Please don't ever do this, this just shows how painful it would be
|
129
|
-
# If you reach the need to do this but won't use where_assoc_exists,
|
130
|
-
# go for a regular #where("EXISTS( SELECT ...)")
|
131
|
-
Post.joins(:comments).where(comments: {is_spam: true})
|
132
|
-
.joins("JOIN comments comments_for_reported ON posts.id = comments_for_reported.post_id")
|
133
|
-
.where(comments_for_reported: {is_reported: true})
|
134
|
-
```
|
135
|
-
|
136
|
-
* Cannot be used with Rails 5's `or` unless both side do the same `joins`.
|
137
|
-
* [Treats has_one like a has_many](#treating-has_one-like-has_many)
|
138
|
-
* [Can't handle recursive associations](#unable-to-handle-recursive-associations)
|
139
|
-
|
140
|
-
### Using `includes` (or `eager_load`) and `where`
|
141
|
-
|
142
|
-
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.
|
143
|
-
|
144
|
-
```ruby
|
145
|
-
Post.where_assoc_exists(:comments, is_spam: true)
|
146
|
-
Post.eager_load(:comments).where(comments: {is_spam: true})
|
147
|
-
```
|
148
|
-
|
149
|
-
* 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.
|
150
|
-
|
151
|
-
* 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.
|
152
|
-
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.
|
153
|
-
|
154
|
-
* Cannot be used with Rails 5's `or` unless both side do the same `includes` or `eager_load`.
|
155
|
-
|
156
|
-
* [Treats has_one like a has_many](#treating-has_one-like-has_many)
|
157
|
-
* [Can't handle recursive associations](#unable-to-handle-recursive-associations)
|
158
|
-
|
159
|
-
* Simply cannot be used for complex cases.
|
160
|
-
|
161
|
-
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):
|
162
|
-
|
163
|
-
```ruby
|
164
|
-
Post.where_assoc_exists(:comments)
|
165
|
-
Post.eager_load(:comments).where(comments: {id: nil})
|
166
|
-
```
|
167
|
-
|
168
|
-
### Using `where("EXISTS( SELECT... )")`
|
169
|
-
|
170
|
-
This is what is gem does behind the scene, but doing it manually can lead to troubles:
|
171
|
-
|
172
|
-
* Problems with writing [raw SQL sub-selects](#raw-sql-joins-or-sub-selects)
|
173
|
-
|
174
|
-
* Unless you do a quite complex nested sub-selects, you will [treat has_one like a has_many](#treating-has_one-like-has_many)
|
175
|
-
|
176
|
-
|
177
|
-
## Gems
|
178
|
-
|
179
|
-
### where_exists
|
180
|
-
|
181
|
-
https://github.com/EugZol/where_exists
|
182
|
-
|
183
|
-
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.
|
184
|
-
|
185
|
-
* where_exists supports polymorphic belongs_to. This is something that where_assoc doesn't do at the moment.
|
186
|
-
However, the way it does this is by doing a pluck on the type column, which in some situation could be a slow query if there is a lots of rows to scan.
|
187
|
-
|
188
|
-
* Unable to use scopes of the association's model.
|
189
|
-
```ruby
|
190
|
-
# There is no equivalent for this (admins is a scope on User)
|
191
|
-
Comment.where_assoc_exists(:author, &:admins)
|
192
|
-
```
|
193
|
-
|
194
|
-
* Cannot use a block for more complex conditions
|
195
|
-
```ruby
|
196
|
-
# There is no equivalent for this
|
197
|
-
Comment.where_assoc_exists(:author) { admins.where("created_at <= ?", 1.month.ago) }
|
198
|
-
```
|
199
|
-
|
200
|
-
* Unable to dig deeper in the associations
|
201
|
-
Note: it does follow :through associations so doing a custom associations for your need can be a workaround.
|
202
|
-
|
203
|
-
```ruby
|
204
|
-
# There is no equivalent for this (Users that have posts with at least a comments)
|
205
|
-
User.where_assoc_exists([:posts, :comments])
|
206
|
-
```
|
207
|
-
|
208
|
-
* Has no equivalent to `where_assoc_count`
|
209
|
-
```ruby
|
210
|
-
# There is no equivalent for this (posts with more than 5 comments)
|
211
|
-
Post.where_assoc_count(:comments, :>, 5)
|
212
|
-
```
|
213
|
-
|
214
|
-
* [Treats has_one like a has_many](#treating-has_one-like-has_many)
|
215
|
-
|
216
|
-
* [Can't handle recursive associations](#unable-to-handle-recursive-associations)
|
217
|
-
|
218
|
-
* `where_exists` is shorter than `where_assoc_exists`, but it is also less obvious about what it does.
|
219
|
-
In any case, it is trivial to alias one name to the other one.
|
220
|
-
|
221
|
-
* where_exists supports Rails 4.2 and up, while where_assoc supports Rails 4.1 and up.
|
@@ -1,180 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "active_record_compat"
|
4
|
-
require_relative "exceptions"
|
5
|
-
|
6
|
-
module ActiveRecordWhereAssoc
|
7
|
-
module QueryMethods
|
8
|
-
# Returns a new relation, which is the result of filtering the current relation
|
9
|
-
# based on if a record for the specified association of the model exists. Conditions
|
10
|
-
# the associated model must match to count as existing can also be specified.
|
11
|
-
#
|
12
|
-
# Here is a quick overview of the arguments received followed by a detailed explanation
|
13
|
-
# along with more examples. You may also consider viewing the gem's README. It contains
|
14
|
-
# known issues and some tips. The readme is packaged with the gem and viewable on github:
|
15
|
-
# https://github.com/MaxLap/activerecord_where_assoc
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# As 1st argument, you must specify the association to check against. This can be
|
19
|
-
# any of the associations on the current relation's model.
|
20
|
-
#
|
21
|
-
# # Posts that have at least one comment
|
22
|
-
# Post.where_assoc_exists(:comments)
|
23
|
-
#
|
24
|
-
# As 2nd argument, you can add conditions that the records in the association must match
|
25
|
-
# to be considered as existing.
|
26
|
-
#
|
27
|
-
# The 3rd argument is for options that alter how the query is generated.
|
28
|
-
#
|
29
|
-
# If your conditions are too complex or too long to be placed in the 2nd argument,
|
30
|
-
# #where_assoc_* accepts a block in which you can do anything you want on the relation
|
31
|
-
# (any scoping method such as #where, #joins, nested #where_assoc_*, scopes of the model).
|
32
|
-
#
|
33
|
-
# === the association argument (1st argument)
|
34
|
-
#
|
35
|
-
# This is the association you want to check if records exists. If you want, you can pass
|
36
|
-
# an array of associations. They will be followed in order, just like a has_many :through
|
37
|
-
# would.
|
38
|
-
#
|
39
|
-
# # Posts with at least one comment
|
40
|
-
# Post.where_assoc_exists(:comments)
|
41
|
-
#
|
42
|
-
# # Posts for which there is at least one reply to a comment.
|
43
|
-
# Post.where_assoc_exists([:comments, :replies])
|
44
|
-
#
|
45
|
-
# Note that if you use conditions / blocks, they will only be applied to the last
|
46
|
-
# association of the array. If you want something else, you will need to use
|
47
|
-
# the block argument to nest multiple calls to #where_assoc_exists
|
48
|
-
#
|
49
|
-
# # Post.where_assoc_exists(:comments) { where_assoc_exists(:replies) }
|
50
|
-
#
|
51
|
-
# === the condition argument (2nd argument)
|
52
|
-
#
|
53
|
-
# This argument is additional conditions the association's records must fulfill to be
|
54
|
-
# considered as "existing". The argument is passed directly to #where.
|
55
|
-
#
|
56
|
-
# # Posts that have at least one comment considered as spam
|
57
|
-
# # Using a Hash
|
58
|
-
# Post.where_assoc_exists(:comments, is_spam: true)
|
59
|
-
#
|
60
|
-
# # Using a String
|
61
|
-
# Post.where_assoc_exists(:comments, "is_spam = true")
|
62
|
-
#
|
63
|
-
# # Using an Array (a string and its binds)
|
64
|
-
# Post.where_assoc_exists(:comments, ["is_spam = ?", true])
|
65
|
-
#
|
66
|
-
# If the condition argument is blank, it is ignored (just like #where does).
|
67
|
-
#
|
68
|
-
# === the options argument (3rd argument)
|
69
|
-
#
|
70
|
-
# Some options are available to tweak how things queries are generated. In some case, this
|
71
|
-
# also changes the results of the query.
|
72
|
-
#
|
73
|
-
# ignore_limit: when true, #limit and #offset that are set either from default_scope or
|
74
|
-
# on associations are ignored. #has_one means #limit(1), so this makes
|
75
|
-
# #has_one be treated like #has_many.
|
76
|
-
#
|
77
|
-
# never_alias_limit: when true, #where_assoc_* will not use #from to build relations that
|
78
|
-
# have #limit or #offset set on default_scope or on associations.
|
79
|
-
# Note, #has_one means #limit(1), so it will also use #from unless this
|
80
|
-
# option is activated.
|
81
|
-
#
|
82
|
-
# === the block
|
83
|
-
#
|
84
|
-
# The block is used to add more complex conditions. The result behaves the same way
|
85
|
-
# as the 2nd argument's conditions, but lets you use any scoping methods, such as
|
86
|
-
# #where, #joins, # nested #where_assoc_* and scopes of the model. Note that using
|
87
|
-
# #joins might lead to unexpected results when using #where_assoc_count, since if
|
88
|
-
# the joins adds rows, it will change the resulting count.
|
89
|
-
#
|
90
|
-
# There are 2 ways of using the block for adding conditions to the association.
|
91
|
-
#
|
92
|
-
# * A block that receives one argument
|
93
|
-
# The block receives a relation on the target association and return a relation with added
|
94
|
-
# filters or may return nil to do nothing.
|
95
|
-
#
|
96
|
-
# # Using a where for the added condition
|
97
|
-
# Post.where_assoc_exists(:comments) { |comments| comments.where(is_spam: true) }
|
98
|
-
#
|
99
|
-
# # Applying a scope of the relation
|
100
|
-
# Post.where_assoc_exists(:comments) { |comments| comments.spam_flagged }
|
101
|
-
#
|
102
|
-
# # Applying a scope of the relation, using the &:shortcut for procs
|
103
|
-
# Post.where_assoc_exists(:comments, &:spam_flagged)
|
104
|
-
#
|
105
|
-
#
|
106
|
-
# * A block that receives no argument
|
107
|
-
# Instead of receiving the relation as argument, the relation is used as the "self" of
|
108
|
-
# the block. Everything else is identical to the block with one argument.
|
109
|
-
#
|
110
|
-
# # Using a where for the added condition
|
111
|
-
# Post.where_assoc_exists(:comments) { where(is_spam: true) }
|
112
|
-
#
|
113
|
-
# # Applying a scope of the relation
|
114
|
-
# Post.where_assoc_exists(:comments) { spam_flagged }
|
115
|
-
#
|
116
|
-
# The main reason to use a block with an argument instead of without is when you need
|
117
|
-
# to call methods on the self outside of the block, such as:
|
118
|
-
#
|
119
|
-
# Post.where_assoc_exists(:comments) { |comments| comments.where(id: self.something) }
|
120
|
-
#
|
121
|
-
def where_assoc_exists(association_name, given_scope = nil, options = {}, &block)
|
122
|
-
ActiveRecordWhereAssoc::CoreLogic.do_where_assoc_exists(self, association_name, given_scope, options, &block)
|
123
|
-
end
|
124
|
-
|
125
|
-
# Returns a new relation, which is the result of filtering the current relation
|
126
|
-
# based on if a record for the specified association of the model doesn't exist.
|
127
|
-
# Conditions the associated model must match to count as existing can also be specified.
|
128
|
-
#
|
129
|
-
# The parameters and everything is identical to #where_assoc_exists. The only
|
130
|
-
# difference is that a record is matched if no matching association record that
|
131
|
-
# fulfill the conditions are found.
|
132
|
-
def where_assoc_not_exists(association_name, given_scope = nil, options = {}, &block)
|
133
|
-
ActiveRecordWhereAssoc::CoreLogic.do_where_assoc_not_exists(self, association_name, given_scope, options, &block)
|
134
|
-
end
|
135
|
-
|
136
|
-
# Returns a new relation, which is the result of filtering the current relation
|
137
|
-
# based on how many records for the specified association of the model exists. Conditions
|
138
|
-
# the associated model must match can also be specified.
|
139
|
-
#
|
140
|
-
# #where_assoc_count is a generalization of #where_assoc_exists and #where_assoc_not_exists.
|
141
|
-
# It behave behaves the same way as them, but is more flexible as it allows you to be
|
142
|
-
# specific about how many matches there should be. To clarify, here are equivalent examples:
|
143
|
-
#
|
144
|
-
# Post.where_assoc_exists(:comments)
|
145
|
-
# Post.where_assoc_count(1, :<=, :comments)
|
146
|
-
#
|
147
|
-
# Post.where_assoc_not_exists(:comments)
|
148
|
-
# Post.where_assoc_count(0, :==, :comments)
|
149
|
-
#
|
150
|
-
# The usage is the same as with #where_assoc_exists, however, 2 arguments are inserted
|
151
|
-
# at the beginning.
|
152
|
-
#
|
153
|
-
# 1st argument: the left side of the comparison. One of:
|
154
|
-
# a number
|
155
|
-
# a string of SQL to embed in the query
|
156
|
-
# a range (operator must be :== or :!=), will use BETWEEN or NOT BETWEEN
|
157
|
-
# supports infinite ranges and exclusive end
|
158
|
-
# 2nd argument: the operator to use: :<, :<=, :==, :!=, :>=, :>
|
159
|
-
# 3rd, 4th and 5th arguments: same as #where_assoc_exists' 1st, 2nd and 3rd arguments
|
160
|
-
# block: same as #where_assoc_exists' block
|
161
|
-
#
|
162
|
-
# The order of the parameters may seem confusing. But you will get used to it. To help
|
163
|
-
# remember the order of the parameters, remember that the goal is to do:
|
164
|
-
# 5 < (SELECT COUNT(*) FROM ...)
|
165
|
-
# So the parameters are in the same order as in that query: number, operator, association.
|
166
|
-
#
|
167
|
-
# To be clear, when you use multiple associations in an array, the count you will be
|
168
|
-
# comparing against is the total number of records of that last association.
|
169
|
-
#
|
170
|
-
# # The users that have received at least 5 comments total on all of their posts
|
171
|
-
# # So this can be one post that has 5 comments of 5 posts with 1 comments
|
172
|
-
# User.where_assoc_count(5, :<=, [:posts, :comments])
|
173
|
-
#
|
174
|
-
# # The users that have at least 5 posts with at least one comments
|
175
|
-
# User.where_assoc_count(5, :<=, :posts) { where_assoc_exists(:comments) }
|
176
|
-
def where_assoc_count(left_operand, operator, association_name, given_scope = nil, options = {}, &block)
|
177
|
-
ActiveRecordWhereAssoc::CoreLogic.do_where_assoc_count(self, left_operand, operator, association_name, given_scope, options, &block)
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|