activerecord-refined 0.10.2 → 0.11.0

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: 9270b5bf04c083f8cefdd5c669810a63a94a2073c1cae49ba28027693ee5b684
4
- data.tar.gz: 23f7e622c8b6b1e85cf670798b705aec8e57c0149315c5fb50699fc95d10ee18
3
+ metadata.gz: 0f77f34a31719d6697c0d77c378392a6bffa95f033345272e981c51f3fb6f977
4
+ data.tar.gz: bed64c1983fd76634d924a28a690e3b54705f9a948bf06735314ddd91bed0037
5
5
  SHA512:
6
- metadata.gz: dcd5cbfab0c7d60498b21a25240f29ae224aa6de7b5fd0a37c627ee10e0f642ed4bb66740734bca03f64f3b231032e273163d68fc9de59b3c07447bef1f69eab
7
- data.tar.gz: e701353709c2f8dfb7ebc38a9bdfb9e8b56c940579f139beed543f20391cf8d50166391c3de1718d85797185f4bcfb0cec3a3f4a2d91948c1344527f0ea18e6e
6
+ metadata.gz: cd03b6d8647c0aef54927c5c68f6c7145ebfcdfa1223abc9b9650905dbaf21281b05678cd708889297a75dfc388e17b886358ebd6db445948b209d1e84d9f6f1
7
+ data.tar.gz: 2d971fb7ea3f757569b3a8e0127c52693afd20a5eeb15523dd58d2747376f2e426833ef71a343c880e9d225fb9f1a37f1a3f16f1998ac96fb6341e7c5f3f8fb6
data/docs/ctes.md CHANGED
@@ -18,11 +18,11 @@ Node.with_recursive(
18
18
  ]
19
19
  ).from_cte(:tree)
20
20
  # WITH RECURSIVE "tree" AS (
21
- # SELECT "nodes"."id", "nodes"."name", "nodes"."parent_id", 0 AS depth
21
+ # SELECT "nodes"."id", "nodes"."name", "nodes"."parent_id", 0 AS "depth"
22
22
  # FROM "nodes" WHERE "nodes"."id" = 1
23
23
  # UNION ALL
24
24
  # SELECT "nodes"."id", "nodes"."name", "nodes"."parent_id",
25
- # ("tree"."depth" + 1) AS depth
25
+ # ("tree"."depth" + 1) AS "depth"
26
26
  # FROM "nodes" INNER JOIN "tree" ON "nodes"."parent_id" = "tree"."id"
27
27
  # ) SELECT "nodes".* FROM "tree" AS "nodes"
28
28
  ```
data/docs/expressions.md CHANGED
@@ -20,21 +20,35 @@ Item.select { greatest(20 - :quantity, 0).as(:shortfall) }
20
20
  Item.where { BigDecimal("1.08") * :price > 500 }
21
21
  ```
22
22
 
23
- `&`, `|`, `^`, `~`, `<<` and `>>` are SQL's bitwise operators. Between
24
- conditions `&` and `|` are AND and OR, and that is where they are defined,
25
- which leaves them free to mean here what SQL means by them:
23
+ `bitwise_and`, `bitwise_or`, `^`, `~`, `<<` and `>>` are SQL's bitwise
24
+ operations. The first two are named rather than spelled `&` and `|`, which
25
+ mean AND and OR between conditions and nothing else anywhere; `bitwise_xor`
26
+ and `bitwise_not` stand beside `^` and `~` for a reader who would rather have
27
+ the name:
26
28
 
27
29
  ```ruby
28
- Post.where { :flags & 4 > 0 }
30
+ Post.where { :flags.bitwise_and(4) > 0 }
29
31
  # WHERE ("posts"."flags" & 4) > 0
30
32
 
31
- Post.select { (:flags | 4).as(:flags) }
33
+ Post.select { :flags.bitwise_or(4).as(:flags) }
32
34
  Post.select { (~:flags).as(:inverted) }
33
35
  ```
34
36
 
35
- Each parenthesises itself, which is what keeps Ruby's grouping: PostgreSQL
36
- gives `&` and `|` the same precedence and reads `a | b & c` from the left,
37
- where Ruby reads the `&` first.
37
+ A method binds tighter than any comparison, so nothing has to be parenthesised
38
+ to be compared. Each operation parenthesises itself in the SQL as well, since
39
+ PostgreSQL gives `&` and `|` the same precedence and reads `a | b & c` from
40
+ the left.
41
+
42
+ Reaching for the operator on a column says which name was meant:
43
+
44
+ ```ruby
45
+ Post.where { :flags & 4 }
46
+ # ArgumentError: & between conditions is AND; bitwise_and is SQL's bitwise operator
47
+ ```
48
+
49
+ Oracle has none of the operators — its one bit operation is the `BITAND`
50
+ function — and the block refuses every one there rather than leaving its
51
+ server to.
38
52
 
39
53
  A boolean column is refused rather than taken for the one bit it is stored as.
40
54
  MySQL and SQLite would quietly answer as `AND` would, PostgreSQL has no such
@@ -79,12 +93,12 @@ since a literal that has been sent `as` has already said it is a value:
79
93
 
80
94
  ```ruby
81
95
  Node.select { [:id, value(0).as(:depth)] }
82
- # SELECT "nodes"."id", 0 AS depth FROM "nodes"
96
+ # SELECT "nodes"."id", 0 AS "depth" FROM "nodes"
83
97
 
84
98
  Node.select { [:id, 0.as(:depth)] } # the same thing
85
99
 
86
100
  Post.select { [:title, "draft".as(:state)] }
87
- # SELECT "posts"."title", 'draft' AS state FROM "posts"
101
+ # SELECT "posts"."title", 'draft' AS "state" FROM "posts"
88
102
  ```
89
103
 
90
104
  What the shorthand does not cover, `value` still spells: `value(true)`,
@@ -98,10 +112,10 @@ that does not need it:
98
112
 
99
113
  ```ruby
100
114
  Author.select { :country.when("JP").then("Japan").else("elsewhere").as(:where) }
101
- # CASE "country" WHEN 'JP' THEN 'Japan' ELSE 'elsewhere' END AS where
115
+ # CASE "country" WHEN 'JP' THEN 'Japan' ELSE 'elsewhere' END AS "where"
102
116
 
103
117
  Author.select { case_when { :age >= 60 }.then("senior").else("adult").as(:band) }
104
- # CASE WHEN "age" >= 60 THEN 'senior' ELSE 'adult' END AS band
118
+ # CASE WHEN "age" >= 60 THEN 'senior' ELSE 'adult' END AS "band"
105
119
 
106
120
  Author.select { self.case(mod(:age, 10)).when(0).then("round").else("not").as(:v) }
107
121
  ```
@@ -121,5 +135,5 @@ Author.select {
121
135
  }
122
136
 
123
137
  Author.select { sum(case_when { :age >= 60 }.then(1).else(0)).as(:seniors) }
124
- # SUM(CASE WHEN "age" >= 60 THEN 1 ELSE 0 END) AS seniors
138
+ # SUM(CASE WHEN "age" >= 60 THEN 1 ELSE 0 END) AS "seniors"
125
139
  ```
data/docs/functions.md CHANGED
@@ -96,7 +96,7 @@ written, so a case-sensitive one can be spelled exactly:
96
96
 
97
97
  ```ruby
98
98
  Post.select { fn(:date_trunc, "day", :created_at).as(:day) }
99
- # SELECT date_trunc('day', "posts"."created_at") AS day
99
+ # SELECT date_trunc('day', "posts"."created_at") AS "day"
100
100
  ```
101
101
 
102
102
  `op` is the same escape hatch for operators — PostgreSQL alone has dozens
@@ -107,10 +107,10 @@ allows an operator, so a letter, a space or a quote is refused rather than
107
107
  written into the SQL. Both sides take what `fn`'s arguments take — a
108
108
  column, an expression, a value quoted by the adapter — and a value is
109
109
  spelled in the adapter's own syntax, `to_json` saying a document, `'{a,b}'`
110
- an array; a Ruby Hash or Array is refused rather than guessed at. The
111
- result is parenthesized, its precedence being unknown, and so is an
112
- expression on either side, so a dug value cannot be re-grouped out from
113
- under it:
110
+ an array; a Ruby Hash or Array is refused rather than guessed at. What it
111
+ gives combines with `&`, `|` and `!` as a condition does. The result is
112
+ parenthesized, its precedence being unknown, and so is an expression on
113
+ either side, so a dug value cannot be re-grouped out from under it:
114
114
 
115
115
  ```ruby
116
116
  Post.where { op("&&", :tags, "{ruby,sql}") }
@@ -127,15 +127,19 @@ for by name, and an interpolation has a spelling that is not it: `?` and
127
127
  `:name` placeholders take values quoted by the adapter, through
128
128
  `sanitize_sql_array`. A `?` is rewritten only when there are positional binds
129
129
  to put in it, so PostgreSQL's `?` operators can share a statement with named
130
- binds, or with none. The result carries the predications and arithmetic, and
131
- is parenthesized where it stands inside a larger expression its precedence
132
- is whatever was written but comes out bare at the top of a select list,
133
- where parentheses would refuse an alias written into the string:
130
+ binds, or with none. The result carries the predications and arithmetic,
131
+ combines with `&`, `|` and `!` as a condition does, and is parenthesized
132
+ where it stands inside a larger expression its precedence is whatever was
133
+ written but comes out bare at the top of a select list, where parentheses
134
+ would refuse an alias written into the string:
134
135
 
135
136
  ```ruby
136
137
  Post.where { sql("length(title) > ?", 10) }
137
138
  # WHERE (length(title) > 10)
138
139
 
140
+ Post.where { sql("score > 0") & (:published == true) }
141
+ # WHERE (score > 0) AND "posts"."published" = TRUE
142
+
139
143
  Post.where { sql("score + ?", 10) * 2 >= 60 }
140
144
  # WHERE (score + 10) * 2 >= 60
141
145
 
@@ -195,7 +199,7 @@ Post.where { extract(:year, :created_at) == 2026 }
195
199
  # SELECT "posts".* FROM "posts" WHERE EXTRACT(YEAR FROM "posts"."created_at") = 2026
196
200
 
197
201
  Post.select { cast(:price, "decimal(10,2)").as(:price) }
198
- # SELECT CAST("posts"."price" AS decimal(10,2)) AS price
202
+ # SELECT CAST("posts"."price" AS decimal(10,2)) AS "price"
199
203
  ```
200
204
 
201
205
  A duration moves a date. Active Support's `7.days` and the rest go on the
data/docs/index.md CHANGED
@@ -11,17 +11,6 @@ Author.
11
11
  # WHERE "authors"."age" BETWEEN 20 AND 40 AND "posts"."published" = TRUE
12
12
  ```
13
13
 
14
- Inside a block, symbols denote columns of the receiver's table, and
15
- `:table[:column]` denotes a qualified column. That holds in every position —
16
- on the right of a comparison too, so `:age == :retirement_age` compares two
17
- columns. A value is written as its literal, an enum's as its string; a symbol
18
- naming no column of the model is refused rather than compared against nothing
19
- anyone meant.
20
-
21
- **[Try it in your browser](https://shugo.github.io/activerecord-refined/)** —
22
- Ruby 4.1, Active Record, SQLite and PostgreSQL run in the page, so the examples
23
- build real SQL and return real rows without a `ruby-master` build of your own.
24
-
25
14
  ## Reference
26
15
 
27
16
  - {ActiveRecord::Refined::BlockSyntax BlockSyntax} — what a symbol answers to
@@ -50,7 +39,7 @@ differ:
50
39
  - {file:docs/functions.md Aggregates and functions} — `count`, `filter`,
51
40
  `string_agg`, the scalar functions, the clock, durations, `extract` and
52
41
  `cast`.
53
- - {file:docs/expressions.md Expressions} — arithmetic, the bitwise operators,
42
+ - {file:docs/expressions.md Expressions} — arithmetic, the bitwise operations,
54
43
  `CASE`.
55
44
  - {file:docs/json.md JSON} — `dig`, `bury`, `except`, `key?`, `keys`, the
56
45
  JSON aggregates, and where the adapters' JSON types part.
@@ -64,7 +53,3 @@ differ:
64
53
  - {file:docs/writing.md Writing} — `update_all` and `upsert_all`.
65
54
  - {file:docs/time_zones.md Time zones} — what a block converts and what it
66
55
  leaves to the session.
67
-
68
- The repository's README, on
69
- [GitHub](https://github.com/shugo/activerecord-refined), has the history,
70
- the requirements, and how the tests and the release are run.
data/docs/windows.md CHANGED
@@ -7,10 +7,10 @@ The window is built by chaining, as Arel's own is:
7
7
 
8
8
  ```ruby
9
9
  Author.select { avg(:age).over.partition(:country).as(:country_average) }
10
- # AVG("age") OVER (PARTITION BY "country") AS country_average
10
+ # AVG("age") OVER (PARTITION BY "country") AS "country_average"
11
11
 
12
12
  Author.select { row_number.over.partition(:country).order(:age.desc).as(:rank) }
13
- # ROW_NUMBER() OVER (PARTITION BY "country" ORDER BY "age" DESC) AS rank
13
+ # ROW_NUMBER() OVER (PARTITION BY "country" ORDER BY "age" DESC) AS "rank"
14
14
 
15
15
  Author.select { count(:*).over.as(:total) } # COUNT(*) OVER () — every row
16
16
  ```
@@ -82,12 +82,12 @@ show("a due date a month on",
82
82
  LineItem.select { [:sku, (:ordered_on + 1.month).as(:due_on)] }.
83
83
  map { |i| [i.sku, i.due_on] })
84
84
 
85
- # The bitwise operators. & and | are AND and OR between conditions, which is
86
- # what leaves them free here. Each expression parenthesises itself, so the
87
- # grouping is Ruby's rather than the adapter's.
85
+ # The bitwise operations. AND and OR are what & and | mean, so those two go
86
+ # by name here; a method binds tighter than any comparison, and the expression
87
+ # parenthesises itself in the SQL so the adapter cannot regroup it.
88
88
  show("a bit test in a condition",
89
- LineItem.where { :flags & 4 > 0 },
90
- LineItem.where { :flags & 4 > 0 }.pluck(:sku))
89
+ LineItem.where { :flags.bitwise_and(4) > 0 },
90
+ LineItem.where { :flags.bitwise_and(4) > 0 }.pluck(:sku))
91
91
 
92
92
  # XOR is the one the three adapters do not share. SQLite has none, so it gets
93
93
  # the two operations XOR is made of; PostgreSQL would say #, MySQL ^.
@@ -99,9 +99,9 @@ show("xor, spelled the way the adapter spells it",
99
99
  # the three adapters would quietly answer as AND does and the third has no
100
100
  # such operator, so the block refuses instead.
101
101
  begin
102
- LineItem.where { :flags & (:price == 1) }
102
+ LineItem.where { :flags.bitwise_and(:price == 1) }
103
103
  rescue ArgumentError => e
104
- puts "--- a condition is not an operand of & ---"
104
+ puts "--- a condition is not an operand of a bitwise operation ---"
105
105
  puts " #{e.message}"
106
106
  puts
107
107
  end
@@ -184,6 +184,12 @@ show("sql, for what neither fn nor op can spell",
184
184
  LineItem.where { sql("price % ?", 100) == 0 },
185
185
  LineItem.where { sql("price % ?", 100) == 0 }.pluck(:sku))
186
186
 
187
+ # What sql or op gives is a condition when what it holds is one, and
188
+ # combines with & | ! like any other.
189
+ show("an sql condition combined with a built one",
190
+ LineItem.where { sql("quantity > 4") & (:category == "tools") },
191
+ LineItem.where { sql("quantity > 4") & (:category == "tools") }.pluck(:sku))
192
+
187
193
  # A string sent `as` is a value, like a number.
188
194
  show("a string literal in a select list",
189
195
  LineItem.select { [:sku, "listed".as(:state)] },