activerecord_where_assoc 0.1.1 → 0.1.2

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: e248bec95c932b0bd11743e33d38915ebc13c398
4
- data.tar.gz: bea0b06ff2f9e3f61a92e4e2f0cab78daaf63b9b
2
+ SHA256:
3
+ metadata.gz: 78be7fdd6b3da54253f5fe51709c258256dbe013341b69c3f0c9a23a3e2968f3
4
+ data.tar.gz: 69c40eabe1332c945abef850347d2b39dc4412eedc65cc02aabcd07901ef2de5
5
5
  SHA512:
6
- metadata.gz: 9551db7bfb1f1219dd1c23fe9255e4ffd495a4bf6777c53619d7037b432e1b13ecafeabee6be8083d8556bca49cf6473f3fbd4ff601723a1380f953a9dee764b
7
- data.tar.gz: 5b1d53119ce95d0ab40665330f63c58512818f4af16352e5eb6a7245853ed15c40c2391152aa08bb894703ef853b775c94d3302c5641ae4372d75dfbda2e91fa
6
+ metadata.gz: f004ca618191fe6b8841d354ef10b3e0209e6721f5d35d5ebc6aff87f13fcbf62a92e0cd42bd77ef4b87b987310a56155bab9f50b59915b361871c3d51cebe4a
7
+ data.tar.gz: 84da6aaedd53c494913bad518a36ca18dd717c3ac2d3dbed3af5fe78561716ad0c38af67832272ea2b26dddd0319f5ae820a1d636cea5fb14aa27a8814a2f2dc
@@ -21,18 +21,17 @@ Summary of the problems of the alternatives that `activerecord_where_assoc` solv
21
21
  * every alternatives (except raw SQL):
22
22
  * treat `has_one` like a `has_many`.
23
23
  * can't handle recursive associations. (ex: parent/children)
24
- * no simple way of checking for more complex counts. (such as less than 5)
24
+ * no simple way of checking for more complex counts. (such as `less than 5`)
25
25
  * `joins` / `includes`:
26
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 becomes incompatible)
27
+ * checking for 2 sets of conditions on different records of the same association won't work. (so your scopes can be incompatible)
28
28
  * can't be used with Rails 5's `or` unless both sides do the same `joins` / `includes` / `eager_load`.
29
29
  * `joins`:
30
- * `has_many` may return duplicate rows per record.
30
+ * `has_many` may return duplicate records.
31
31
  * using `uniq` / `distinct` to solve duplicate rows is an unexpected side-effect when this is in a scope.
32
32
  * `includes`:
33
33
  * triggers eagerloading, which makes your `scope` have unexpected bad performances if it's not necessary.
34
34
  * when using a condition, the eagerloaded records are also filtered, which is very bug-prone when in a scope.
35
- * can't do `not exists` with conditions.
36
35
  * raw SQL:
37
36
  * verbose, less clear on the goal of the queries (you don't even name the association the query is about).
38
37
  * need to repeat conditions from the association / default_scope.
@@ -1,6 +1,6 @@
1
1
  Here are some example usages of the gem, along with the generated SQL. Each of those can be chained with scoping methods.
2
2
 
3
- Models can be found in [examples/models.md](examples/models.md). Explanation is provided in that file to be able to run these queries.
3
+ The models can be found in [examples/models.md](examples/models.md). The comments in that file explain how to get a console to try the queries. There are also example uses of the gem for scopes.
4
4
 
5
5
  The content below is generated from running `ruby examples/examples.rb`
6
6
 
data/README.md CHANGED
@@ -80,7 +80,7 @@ Post.where_assoc_not_exists(:comments, is_spam: true)
80
80
 
81
81
  ### `#where_assoc_count`
82
82
 
83
- This is a generalization of `#where_assoc_exists` and `#where_assoc_not_exists`. It behave behaves the same way as them, but is more flexible as it allows you to be specific about how many matches there should be. To clarify, here are equivalent examples:
83
+ This is a generalization of `#where_assoc_exists` and `#where_assoc_not_exists`. It behaves the same way as them, but is more flexible as it allows you to be specific about how many matches there should be. To clarify, here are equivalent examples:
84
84
 
85
85
  ```ruby
86
86
  Post.where_assoc_exists(:comments, is_spam: true)
@@ -90,7 +90,12 @@ Post.where_assoc_not_exists(:comments, is_spam: true)
90
90
  Post.where_assoc_count(0, :==, :comments, is_spam: true)
91
91
  ```
92
92
 
93
- * 1st parameter: a number or any string of SQL to embed in the query used for the leftoperand of the comparison.
93
+ * 1st parameter: the left side of the comparison. One of:
94
+ * a number
95
+ * a string of SQL to embed in the query
96
+ * a range (operator must be `:==` or `:!=`)
97
+ will use SQL's `BETWEEN` or `NOT BETWEEN`
98
+ supports infinite ranges and exclusive end
94
99
  * 2nd parameter: the operator to use: `:<`, `:<=`, `:==`, `:!=`, `:>=`, `:>`
95
100
  * 3rd, 4th, 5th parameters are the same as the 1st, 2nd and 3rd parameters of `#where_assoc_exists`.
96
101
  * block: same as `#where_assoc_exists`' block
@@ -69,16 +69,9 @@ module ActiveRecordWhereAssoc
69
69
  end
70
70
 
71
71
  nested_relation = relation_on_association(base_relation, association_name, given_scope, options, deepest_scope_mod, NestWithSumBlock)
72
- operator = case operator.to_s
73
- when "=="
74
- "="
75
- when "!="
76
- "<>"
77
- else
78
- operator
79
- end
80
72
 
81
- base_relation.where("(#{left_operand}) #{operator} COALESCE((#{nested_relation.to_sql}), 0)")
73
+ sql = sql_for_count_operator(left_operand, operator, "COALESCE((#{nested_relation.to_sql}), 0)")
74
+ base_relation.where(sql)
82
75
  end
83
76
 
84
77
  # Returns the receiver (with possible alterations) and a relation meant to be embed in the received.
@@ -333,5 +326,44 @@ module ActiveRecordWhereAssoc
333
326
  parent = ActiveRecordCompat.parent_reflection(reflection)
334
327
  parent && parent.macro == :has_and_belongs_to_many
335
328
  end
329
+
330
+ # Doing (SQL) BETWEEN v1 AND v2, where v2 is infinite means (SQL) >= v1. However,
331
+ # we place the SQL on the right side, so the operator is flipped to become v1 <= (SQL).
332
+ # Doing (SQL) NOT BETWEEN v1 AND v2 where v2 is infinite means (SQL) < v1. However,
333
+ # we place the SQL on the right side, so the operator is flipped to become v1 > (SQL).
334
+ RIGHT_INFINITE_RANGE_OPERATOR_MAP = { "=" => "<=", "<>" => ">" }.freeze
335
+ # We flip the operators to use when it's about the left-side of the range.
336
+ LEFT_INFINITE_RANGE_OPERATOR_MAP = Hash[RIGHT_INFINITE_RANGE_OPERATOR_MAP.map { |k, v| [k, v.tr("<>", "><")] }].freeze
337
+
338
+ RANGE_OPERATOR_MAP = { "=" => "BETWEEN", "<>" => "NOT BETWEEN" }.freeze
339
+
340
+ def self.sql_for_count_operator(left_operand, operator, right_sql)
341
+ operator = case operator.to_s
342
+ when "=="
343
+ "="
344
+ when "!="
345
+ "<>"
346
+ else
347
+ operator.to_s
348
+ end
349
+
350
+ return "(#{left_operand}) #{operator} #{right_sql}" unless left_operand.is_a?(Range)
351
+
352
+ unless %w(= <>).include?(operator)
353
+ raise ArgumentError, "Operator should be one of '==', '=', '<>' or '!=' when using a Range not: #{operator.inspect}"
354
+ end
355
+
356
+ v1 = left_operand.begin || 0
357
+ v2 = left_operand.end || Float::INFINITY
358
+
359
+ v1 = 0 if v1 == -Float::INFINITY
360
+
361
+ return sql_for_count_operator(v1, RIGHT_INFINITE_RANGE_OPERATOR_MAP.fetch(operator), right_sql) if v2 == Float::INFINITY
362
+
363
+ # Its int or a float with no mantissa, exclude_end? means -1
364
+ v2 -= 1 if left_operand.exclude_end? && v2 % 1 == 0
365
+
366
+ "#{right_sql} #{RANGE_OPERATOR_MAP.fetch(operator)} #{v1} AND #{v2}"
367
+ end
336
368
  end
337
369
  end
@@ -150,8 +150,11 @@ module ActiveRecordWhereAssoc
150
150
  # The usage is the same as with #where_assoc_exists, however, 2 arguments are inserted
151
151
  # at the beginning.
152
152
  #
153
- # 1st argument: a number or any string of SQL to embed in the query used for the left
154
- # operand of the comparison.
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
155
158
  # 2nd argument: the operator to use: :<, :<=, :==, :!=, :>=, :>
156
159
  # 3rd, 4th and 5th arguments: same as #where_assoc_exists' 1st, 2nd and 3rd arguments
157
160
  # block: same as #where_assoc_exists' block
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordWhereAssoc
4
- VERSION = "0.1.1".freeze
4
+ VERSION = "0.1.2".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: 0.1.1
4
+ version: 0.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: 2018-04-17 00:00:00.000000000 Z
11
+ date: 2018-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -190,7 +190,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
190
190
  version: '0'
191
191
  requirements: []
192
192
  rubyforge_project:
193
- rubygems_version: 2.6.11
193
+ rubygems_version: 2.7.6
194
194
  signing_key:
195
195
  specification_version: 4
196
196
  summary: Make ActiveRecord do conditions on your associations