activerecord_where_assoc 1.1.0 → 1.1.1
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.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b4af9bc1903c723220337ad86c96b760972d233e978f4f13c4a48bff4bcaf053
|
4
|
+
data.tar.gz: c3ecf63db878f798fbc3bc8d670b64d6f762fedbc7bc66b736e95c7eb8702341
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 144fe868a09d055a6523b99e6548aef6113d17d6e69179da71c9ebaf8fd257197a7b2a39678d8385317f227a72afcc63dd17fb34190e6694c40f3380a00367ff
|
7
|
+
data.tar.gz: 6ff02bae5d25b71be3fdc81b095499cc9a0575cea10eb25372e0d85e541195ba186f7d43cbef5ac342ed81d95b96919e357687ef81f2d100cbdcc9462e03cf32
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 1.1.1 - 2020-04-13
|
4
|
+
|
5
|
+
* Fix handling for ActiveRecord's NullRelation (MyModel.none) in block and association's conditions.
|
6
|
+
|
3
7
|
# 1.1.0 - 2020-02-24
|
4
8
|
|
5
9
|
* Added methods which return the SQL used by this gem: `assoc_exists_sql`, `assoc_not_exists_sql`, `compare_assoc_count_sql`, `only_assoc_count_sql`
|
data/README.md
CHANGED
@@ -10,10 +10,10 @@ This gem makes it easy to do conditions based on the associations of your record
|
|
10
10
|
```ruby
|
11
11
|
# Find my_post's comments that were not made by an admin
|
12
12
|
my_post.comments.where_assoc_not_exists(:author, is_admin: true).where(...)
|
13
|
-
|
13
|
+
|
14
14
|
# Find every posts that have comments by an admin
|
15
15
|
Post.where_assoc_exists([:comments, :author], &:admins).where(...)
|
16
|
-
|
16
|
+
|
17
17
|
# Find my_user's posts that have at least 5 non-spam comments (not_spam is a scope on comments)
|
18
18
|
my_user.posts.where_assoc_count(5, :>=, :comments) { |comments| comments.not_spam }.where(...)
|
19
19
|
```
|
@@ -83,8 +83,8 @@ where_assoc_not_exists(association_name, conditions, options, &block)
|
|
83
83
|
where_assoc_count(left_operand, operator, association_name, conditions, options, &block)
|
84
84
|
```
|
85
85
|
|
86
|
-
* These methods add a condition (a `#where`)
|
87
|
-
* You can specify condition on the association, so you could check only comments that are made by an admin.
|
86
|
+
* These methods add a condition (a `#where`) that checks if the association exists (or not)
|
87
|
+
* You can specify condition on the association, so you could check only for comments that are made by an admin.
|
88
88
|
* Each method returns a new relation, meaning you can chain `#where`, `#order`, `limit`, etc.
|
89
89
|
* common arguments:
|
90
90
|
* association_name: the association we are doing the condition on.
|
@@ -120,6 +120,21 @@ where_assoc_count(left_operand, operator, association_name, conditions, options,
|
|
120
120
|
supports infinite ranges and exclusive end
|
121
121
|
* operator: one of `:<`, `:<=`, `:==`, `:!=`, `:>=`, `:>`
|
122
122
|
|
123
|
+
## Intuition
|
124
|
+
|
125
|
+
Here is the basic intuition for the methods:
|
126
|
+
|
127
|
+
`#where_assoc_exists` filters the models, returning those *where* a record for the *association* matching a condition (by default any record in the association) *exists*.
|
128
|
+
|
129
|
+
`#where_assoc_not_exists` is the exact opposite of `#where_assoc_exists`. Filters the models, returning those *where* a record for the *association* matching a condition (by default any record in the association) do *not exists*
|
130
|
+
|
131
|
+
`#where_assoc_count` the more specific version of `#where_assoc_exists`. Filters the models, returning those *where* a record for the *association* matching a condition (by default any record in the association) do *not exists*
|
132
|
+
|
133
|
+
The condition that you may need on the record can be quite complicated. For this reason, you can pass a block to these methods.
|
134
|
+
The block will receive a relation on records of the association. Your job is then to call `where` and scopes to specify what you want to exist (or to not exist if using `#where_assoc_not_exists`).
|
135
|
+
|
136
|
+
So if you have `User.where_assoc_exists(:comments) {|rel| rel.where("content ilike '%github.com%'") }`, `rel` is a relation is on `Comment`, and you are specifying what you want to exist. So now we are looking for users that made a comment containing 'github.com'.
|
137
|
+
|
123
138
|
## Usage tips
|
124
139
|
|
125
140
|
### Nested associations
|
@@ -13,11 +13,12 @@ module ActiveRecordWhereAssoc
|
|
13
13
|
# => "EXISTS (SELECT... *relation1*) OR EXISTS (SELECT... *relation2*)"
|
14
14
|
def self.sql_for_any_exists(relations)
|
15
15
|
relations = [relations] unless relations.is_a?(Array)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
relations = relations.reject { |rel| rel.is_a?(ActiveRecord::NullRelation) }
|
17
|
+
sqls = relations.map { |rel| "EXISTS (#{rel.select('1').to_sql})" }
|
18
|
+
if sqls.size > 1
|
19
|
+
"(#{sqls.join(" OR ")})" # Parens needed when embedding the sql in a `where`, because the OR could make things wrong
|
20
|
+
elsif sqls.size == 1
|
21
|
+
sqls.first
|
21
22
|
else
|
22
23
|
"0=1"
|
23
24
|
end
|
@@ -30,10 +31,11 @@ module ActiveRecordWhereAssoc
|
|
30
31
|
|
31
32
|
# Returns the SQL for getting the sum of of the received relations
|
32
33
|
# => "SUM((SELECT... *relation1*)) + SUM((SELECT... *relation2*))"
|
33
|
-
def self.sql_for_sum_of_counts(
|
34
|
-
|
34
|
+
def self.sql_for_sum_of_counts(relations)
|
35
|
+
relations = [relations] unless relations.is_a?(Array)
|
36
|
+
relations = relations.reject { |rel| rel.is_a?(ActiveRecord::NullRelation) }
|
35
37
|
# Need the double parentheses
|
36
|
-
|
38
|
+
relations.map { |rel| "SUM((#{rel.to_sql}))" }.join(" + ").presence || "0"
|
37
39
|
end
|
38
40
|
|
39
41
|
# Block used when nesting associations for a where_assoc_count
|
@@ -83,7 +85,7 @@ module ActiveRecordWhereAssoc
|
|
83
85
|
end
|
84
86
|
|
85
87
|
nested_relations = relations_on_association(record_class, association_names, given_conditions, options, deepest_scope_mod, NestWithSumBlock)
|
86
|
-
|
88
|
+
nested_relations = nested_relations.reject { |rel| rel.is_a?(ActiveRecord::NullRelation) }
|
87
89
|
nested_relations.map { |nr| "COALESCE((#{nr.to_sql}), 0)" }.join(" + ").presence || "0"
|
88
90
|
end
|
89
91
|
|
@@ -323,6 +325,9 @@ module ActiveRecordWhereAssoc
|
|
323
325
|
return current_scope.unscope(:limit, :offset, :order)
|
324
326
|
end
|
325
327
|
|
328
|
+
# No need to do transformations if this is already a NullRelation
|
329
|
+
return current_scope if current_scope.is_a?(ActiveRecord::NullRelation)
|
330
|
+
|
326
331
|
current_scope = current_scope.limit(1) if reflection.macro == :has_one
|
327
332
|
|
328
333
|
# Order is useless without either limit or offset
|
@@ -58,8 +58,12 @@ module ActiveRecordWhereAssoc
|
|
58
58
|
# Post.where_assoc_exists([:comments, :replies])
|
59
59
|
#
|
60
60
|
# === Condition
|
61
|
-
# After the +association_name+ argument, you can pass
|
62
|
-
#
|
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.
|
63
67
|
#
|
64
68
|
# This +condition+ argument is passed directly to +#where+, so you can pass in the following:
|
65
69
|
#
|
@@ -91,13 +95,13 @@ module ActiveRecordWhereAssoc
|
|
91
95
|
#
|
92
96
|
# === Block
|
93
97
|
# The block is used to add more complex conditions. The effect is the same as the condition
|
94
|
-
# parameter
|
95
|
-
#
|
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
|
96
100
|
# +#where_assoc_*+, scopes on the model, etc.
|
97
101
|
#
|
98
102
|
# Note that using +#joins+ might lead to unexpected results when using #where_assoc_count,
|
99
103
|
# since if the joins adds rows, it will change the resulting count. It probably makes more
|
100
|
-
# sense to, again, use one of the +where_assoc_*+ methods.
|
104
|
+
# sense to, again, use one of the +where_assoc_*+ methods (they can be nested).
|
101
105
|
#
|
102
106
|
# There are 2 ways of using the block for adding conditions to the association.
|
103
107
|
#
|
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.1.
|
4
|
+
version: 1.1.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: 2020-
|
11
|
+
date: 2020-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|