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: f470695cdbb7503ff9def8cbe5caca7f229fe2fa133227e78f44a6c6da6156bb
4
- data.tar.gz: bcf122af66d2338d345f3e151e060e44a5365b0976bf790e7c066cd9ff05912f
3
+ metadata.gz: b4af9bc1903c723220337ad86c96b760972d233e978f4f13c4a48bff4bcaf053
4
+ data.tar.gz: c3ecf63db878f798fbc3bc8d670b64d6f762fedbc7bc66b736e95c7eb8702341
5
5
  SHA512:
6
- metadata.gz: 666127fc2b91da590c71a8f8e95a612ad03597e5e0ee599239752ed83645e93c67de6fcedfbe70a4a9d23f2cf884ff0c6fc091036d752e40f3fffa186ef1e65f
7
- data.tar.gz: 582bae5889078d7b259d20ca1e38cd061ccfced8d96e58b2323dfbccdb7ac896fdac1c37626b7d73da17490d350d63949ba73297fd20fc1b3e49fba4cea4870c
6
+ metadata.gz: 144fe868a09d055a6523b99e6548aef6113d17d6e69179da71c9ebaf8fd257197a7b2a39678d8385317f227a72afcc63dd17fb34190e6694c40f3380a00367ff
7
+ data.tar.gz: 6ff02bae5d25b71be3fdc81b095499cc9a0575cea10eb25372e0d85e541195ba186f7d43cbef5ac342ed81d95b96919e357687ef81f2d100cbdcc9462e03cf32
@@ -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`) to the relation that checks if the association exists (or not)
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
- sql = relations.map { |ns| "EXISTS (#{ns.select('1').to_sql})" }.join(" OR ")
17
- if relations.size > 1
18
- "(#{sql})" # Needed when embedding the sql in a `where`, because the OR could make things wrong
19
- elsif relations.size == 1
20
- sql
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(nested_scopes)
34
- nested_scopes = [nested_scopes] unless nested_scopes.is_a?(Array)
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
- nested_scopes.map { |ns| "SUM((#{ns.to_sql}))" }.join(" + ").presence || "0"
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 additional conditions the associated
62
- # record must also match to be considered as existing.
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, in that these conditions must be matched for the association to be considered
95
- # to exist, but lets you use any scoping methods, such as +#where+, +#joins+, nested
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
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordWhereAssoc
4
- VERSION = "1.1.0".freeze
4
+ VERSION = "1.1.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.1.0
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-02-24 00:00:00.000000000 Z
11
+ date: 2020-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord