activerecord-refined 0.10.0 → 0.10.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: b5fbffdc14c0f8cb45b09818e5cf8dffae6495f3c685c28ec9a1c90aceb47b1c
4
- data.tar.gz: a20af3e848a3fecf80dd541ea81de1a8a3860ddee7b86c0327f45edb0d5bdb91
3
+ metadata.gz: 29722b871b0d5cbc7a8a35f201c2930f6e252419c0d4097a0f7476e2e5d57c7e
4
+ data.tar.gz: 24a324dcd539b4d8295c297256a8670bbd2359a7495f5c8506020ce4f681ad47
5
5
  SHA512:
6
- metadata.gz: 8098cd09333a48a439c8065f9d666d828af2355bbcbf20d9be6f53700ad8019d51aa6b60b9a00e385ad6b640eb54580b0f81e7b7751f9d315ead52eeb508ef2b
7
- data.tar.gz: 78a59ed6b51183f33d61ba6cde598e07820ed2518736a7486a9dd89c5d13d6a456f788cd313007fc66d3c2369db59e6f89d780901a9b5e0d982eeedce8e8bb35
6
+ metadata.gz: 69ece64722eacc0223671ce291696f7d9dd3feb8714673b0b7e2971a52c5a188154f1b821cc626a87b1236565462a4516f1c19b2ccf7bc7c5b849e7340b56fcf
7
+ data.tar.gz: 2ee8961a088d820e546e9cffe98e0ac836e8fb191b8120beebaa15bcc9e5b11ce8b88a1edccdc0580a12448aa0203a1631bd10bba905dc9ccdc066c6b6d24695
data/.yardopts CHANGED
@@ -1,7 +1,7 @@
1
1
  --markup markdown
2
- --plugin markdown-relative-links
3
2
  --no-private
4
3
  --title "ActiveRecord::Refined"
4
+ --readme docs/index.md
5
5
  lib/**/*.rb
6
6
  -
7
7
  docs/conditions.md
data/README.md CHANGED
@@ -14,42 +14,22 @@ Author.
14
14
  # WHERE "authors"."age" BETWEEN 20 AND 40 AND "posts"."published" = TRUE
15
15
  ```
16
16
 
17
+ Inside a block, symbols denote columns of the receiver's table, and
18
+ `:table[:column]` denotes a qualified column. That holds in every position —
19
+ on the right of a comparison too, so `:age == :retirement_age` compares two
20
+ columns. A value is written as its literal, an enum's as its string; a symbol
21
+ naming no column of the model is refused rather than compared against nothing
22
+ anyone meant.
23
+
17
24
  **[Try it in your browser](https://shugo.github.io/activerecord-refined/)** —
18
25
  Ruby 4.1, Active Record, SQLite and PostgreSQL run in the page, so the examples
19
26
  build real SQL and return real rows without a `ruby-master` build of your own.
20
27
 
21
- ## History
22
-
23
- This gem was formerly known as **activerecord-refinements**, created by Akira Matsuda
24
- to experiment with the initial implementation of Ruby 2.0 Refinements. Because of the
25
- Refinements' spec change, that implementation stopped working on Ruby 2.0.0 stable, and
26
- the project was left dormant for a long time.
27
-
28
- It has now been renamed to **activerecord-refined** and reimplemented on top of
29
- [`Proc#refined`](https://docs.ruby-lang.org/en/master/Proc.html#method-i-refined),
30
- which will be introduced in Ruby 4.1. `Proc#refined` returns a new proc that
31
- is evaluated with the given refinements activated, so a block written by the caller can
32
- be re-interpreted under the query DSL's refinements:
33
-
34
- ```ruby
35
- def evaluate_block(&block)
36
- refined_block = block.refined(ActiveRecord::Refined::BlockSyntax)
37
- BlockContext.new.instance_exec(&refined_block)
38
- end
39
- ```
40
-
41
- This is exactly what the old implementation needed and could not do, so the query syntax
42
- works again without monkey-patching `Symbol` globally.
43
-
44
28
  ## Requirements
45
29
 
46
30
  * Ruby 4.1 or later (for `Proc#refined`; not released yet, so a `ruby-master` build is needed for now)
47
31
  * Active Record 7.0 or later
48
32
 
49
- The [sandbox](https://shugo.github.io/activerecord-refined/) is there to skip
50
- that build: it carries its own Ruby 4.1. `sandbox/` in this repository is what
51
- it is made of.
52
-
53
33
  ## Installation
54
34
 
55
35
  Add this line to your application's Gemfile:
@@ -66,152 +46,26 @@ Or install it yourself as:
66
46
 
67
47
  ## Usage
68
48
 
69
- Just require the gem, and `where`, `select`, `joins`, `left_outer_joins`, `having`,
70
- `order` and `group` will accept a block.
49
+ With Bundler a Rails application there is nothing to write: the gem in
50
+ the Gemfile is loaded, and `where`, `select`, `joins`, `left_outer_joins`,
51
+ `having`, `order` and `group` accept a block. Elsewhere, require it:
71
52
 
72
53
  ```ruby
73
54
  require "activerecord-refined"
74
55
  ```
75
56
 
76
- Inside the block, symbols denote columns of the receiver's table, and `:table[:column]`
77
- denotes a qualified column. That holds in every position — on the right of a
78
- comparison too, so `:age == :retirement_age` compares two columns. A value is
79
- written as its literal, an enum's as its string; a symbol naming no column of
80
- the model is refused rather than compared against nothing anyone meant.
81
-
82
- The reference — every method a symbol answers to inside a block, every
83
- function a block can call, and what the relation takes — is on
84
- [rubydoc.info](https://rubydoc.info/gems/activerecord-refined):
85
- [`BlockSyntax`](https://rubydoc.info/gems/activerecord-refined/ActiveRecord/Refined/BlockSyntax)
86
- for the symbol,
87
- [`BlockContext`](https://rubydoc.info/gems/activerecord-refined/ActiveRecord/Refined/BlockContext)
88
- for the block, and
89
- [`QueryMethods`](https://rubydoc.info/gems/activerecord-refined/ActiveRecord/Refined/QueryMethods)
90
- for the relation. What follows is a tour, a topic at a time; each has a page
91
- of its own under [docs/](docs/) that says the rest.
92
-
93
- ### Conditions
94
-
95
- ```ruby
96
- Author.where { :age.between?(20, 40) & :name.like?("A%") }
97
- Author.where { :country.in?(%w[JP US]) | :country.null? }
98
- Author.where { !:name.start_with?("A") }
99
- Author.where { :id.in?(Post.select(:author_id)) } # IN (subquery)
100
- Author.where { exists?(Post.where { :posts[:author_id] == :authors[:id] }) }
101
- ```
102
-
103
- `&`, `|` and `!` are AND, OR and NOT. A value on the right is quoted as Active
104
- Record quotes it, a column compares against a column, a relation is a
105
- subquery. [docs/conditions.md](docs/conditions.md) has the rest: the
106
- negations SQL spells for itself, `true?` and NULL, regular expressions,
107
- `ANY` and `ALL`, PostgreSQL arrays.
108
-
109
- ### Joins
110
-
111
- ```ruby
112
- Author.joins(:posts) { :posts[:author_id] == :authors[:id] }
113
- Employee.joins(:employees, as: :managers) { :managers[:id] == :employees[:manager_id] }
114
- ```
115
-
116
- `joins`, `left_outer_joins`, `right_outer_joins`, `full_outer_joins` and
117
- `cross_joins` take the `ON` as a block and `as:` for a table alias; a
118
- relation joins as a subquery, and one marked `lateral` as a `LATERAL` one.
119
- [docs/joins.md](docs/joins.md).
120
-
121
- ### Aggregates and functions
122
-
123
- ```ruby
124
- Author.group { :country }.having { count(:*) > 1 }
125
- Author.select { [count(:*).filter { :age < 50 }.as(:young), avg(:age).as(:average)] }
126
- Post.group { :author_id }.select { string_agg(:title, ", ").order(:title).as(:titles) }
127
- Post.where { :created_at > current_timestamp - 7.days }
128
- Post.select { cast(:price, "decimal(10,2)").as(:price) }
129
- ```
130
-
131
- The scalar functions are methods — `upper`, `coalesce`, `round` and the rest
132
- — spelled the adapter's way where the adapters differ, and `fn` reaches any
133
- other by name. [docs/functions.md](docs/functions.md).
134
-
135
- ### Expressions
136
-
137
- ```ruby
138
- LineItem.where { :price * :quantity > 1000 }
139
- Post.where { :flags & 4 > 0 }
140
- Author.select { :country.when("JP").then("Japan").else("elsewhere").as(:where) }
141
- Author.select { case_when { :age >= 60 }.then("senior").else("adult").as(:band) }
142
- ```
143
-
144
- Arithmetic, the bitwise operators and `CASE` are expressions like a column,
145
- so they compare, alias and aggregate. [docs/expressions.md](docs/expressions.md).
146
-
147
- ### JSON
148
-
149
- ```ruby
150
- Doc.where { :meta.dig_text(:author, :name) == "alice" }
151
- Doc.where { :meta.key?(:draft) }
152
- Doc.update_all { { meta: :meta.bury(:author, :name, "alice") } }
153
- Post.group { :author_id }.select { json_arrayagg(:title).as(:titles) }
154
- ```
57
+ ## Documentation
155
58
 
156
- `dig` and `dig_text`, `bury`, `except`, `key?` and `keys` read and change a
157
- document by the names Hash uses; `json_object` and `json_arrayagg` build one. The same block
158
- runs on PostgreSQL's `jsonb`, MySQL's JSON and SQLite's.
159
- [docs/json.md](docs/json.md).
59
+ https://rubydoc.info/gems/activerecord-refined/
160
60
 
161
- ### Window functions
61
+ ## Adapters
162
62
 
163
- ```ruby
164
- Author.select { [:name, row_number.over.partition(:country).order(:age.desc).as(:rank)] }
165
- Post.select { sum(:likes).over.order(:created_at).rows(..0).as(:running_total) }
166
- ```
167
-
168
- `over` on any aggregate or window function, then `partition`, `order`,
169
- `rows` and `range`. [docs/windows.md](docs/windows.md).
170
-
171
- ### Ordering, aliases and collation
172
-
173
- ```ruby
174
- Author.order { :country.asc.nulls_last }
175
- Author.select { upper(:name).as(:author) }
176
- Author.where { :name.collate(:nocase) == "alice" }
177
- ```
178
-
179
- [docs/ordering.md](docs/ordering.md).
180
-
181
- ### Grouping, CTEs and DISTINCT ON
182
-
183
- ```ruby
184
- Sale.group { rollup(:region, :product) }
185
- Post.distinct_on { :author_id }.order { [:author_id, :likes.desc] } # PostgreSQL
186
- Node.with_recursive(tree: [Node.where { :id == 1 }, Node.joins(:tree) { :nodes[:parent_id] == :tree[:id] }]).from_cte(:tree)
187
- ```
188
-
189
- [docs/grouping.md](docs/grouping.md) and [docs/ctes.md](docs/ctes.md).
190
-
191
- ### Writing
192
-
193
- ```ruby
194
- Post.where { :published == true }.update_all { { likes: :likes + 1 } }
195
- Tally.upsert_all(rows, unique_by: :page) { { hits: :hits + excluded(:hits) } }
196
- ```
197
-
198
- [docs/writing.md](docs/writing.md).
199
-
200
- ### Time zones
201
-
202
- Active Record stores in UTC and a `Time` in a block is quoted the way Active
203
- Record quotes one, so `where { :created_at > Time.current - 1.day }` is right
204
- whatever `Time.zone` is; what the database says the time is —
205
- `current_timestamp`, `extract` — is the session's business.
206
- [docs/time_zones.md](docs/time_zones.md).
207
-
208
- ## Other adapters
209
-
210
- SQLite, PostgreSQL, MySQL, MariaDB, Oracle and SQL Server are built in: each
63
+ SQLite, PostgreSQL, MySQL, MariaDB, Oracle and SQL Server are supported: each
211
64
  is a `Dialect`, one class per family of spellings, asked for whatever the
212
- databases write differently. An adapter the gem does not know keeps the
213
- standard spellings, which reach further than you might expect; where they
214
- fall short, a dialect of your own says the rest. Subclass
65
+ databases write differently, so that one block builds the right SQL on all
66
+ six. An adapter the gem does not know keeps the standard spellings, which
67
+ reach further than you might expect; where they fall short, a dialect of
68
+ your own says the rest. Subclass
215
69
  `ActiveRecord::Refined::Dialect` — or the built-in family the database
216
70
  descends from — override only what it spells differently, and register it
217
71
  under the adapter's name:
@@ -278,6 +132,29 @@ once, as normal code is — building query blocks with a string `eval` mints
278
132
  a fresh instruction sequence per pass, each earning a copy of its own, and
279
133
  the memo keeps both alive for the life of the process.
280
134
 
135
+ ## History
136
+
137
+ This gem was formerly known as **activerecord-refinements**, created by Akira Matsuda
138
+ to experiment with the initial implementation of Ruby 2.0 Refinements. Because of the
139
+ Refinements' spec change, that implementation stopped working on Ruby 2.0.0 stable, and
140
+ the project was left dormant for a long time.
141
+
142
+ It has now been renamed to **activerecord-refined** and reimplemented on top of
143
+ [`Proc#refined`](https://docs.ruby-lang.org/en/master/Proc.html#method-i-refined),
144
+ which will be introduced in Ruby 4.1. `Proc#refined` returns a new proc that
145
+ is evaluated with the given refinements activated, so a block written by the caller can
146
+ be re-interpreted under the query DSL's refinements:
147
+
148
+ ```ruby
149
+ def evaluate_block(&block)
150
+ refined_block = block.refined(ActiveRecord::Refined::BlockSyntax)
151
+ BlockContext.new.instance_exec(&refined_block)
152
+ end
153
+ ```
154
+
155
+ This is exactly what the old implementation needed and could not do, so the query syntax
156
+ works again without monkey-patching `Symbol` globally.
157
+
281
158
  ## Running the tests
282
159
 
283
160
  The tests only build SQL, but they need a live connection to do it. SQLite is
@@ -320,11 +197,3 @@ stored anywhere.
320
197
  bundle exec bump patch --tag # or bump {major,minor} etc.
321
198
  git push --follow-tags
322
199
  ```
323
-
324
- ## Contributing
325
-
326
- 1. Fork it
327
- 2. Create your feature branch (`git checkout -b my-new-feature`)
328
- 3. Commit your changes (`git commit -am 'Add some feature'`)
329
- 4. Push to the branch (`git push origin my-new-feature`)
330
- 5. Create new Pull Request
@@ -42,11 +42,7 @@ Gem::Specification.new do |gem|
42
42
  gem.add_development_dependency "rubocop-performance", [">= 0"]
43
43
  gem.add_development_dependency "rubocop-rails", [">= 0"]
44
44
  gem.add_development_dependency "rubocop-md", [">= 0"]
45
- # What renders the reference: `yard server --reload` serves it locally, and
46
- # rubydoc.info renders the same .yardopts. The plugin turns the README's
47
- # relative links to docs/*.md -- which GitHub follows as they are -- into
48
- # links to YARD's file pages; where it is not installed, rubydoc.info among
49
- # them, YARD says so and leaves the links relative.
45
+ # What renders the reference: `yard server --reload` serves it locally,
46
+ # and rubydoc.info renders the same .yardopts.
50
47
  gem.add_development_dependency "yard", [">= 0"]
51
- gem.add_development_dependency "yard-markdown-relative-links", [">= 0"]
52
48
  end
data/docs/index.md ADDED
@@ -0,0 +1,70 @@
1
+ # ActiveRecord::Refined
2
+
3
+ Adding clean and powerful query syntax on Active Record using refinements.
4
+
5
+ ```ruby
6
+ Author.
7
+ joins(:posts) { :posts[:author_id] == :authors[:id] }.
8
+ where { :authors[:age].in?(20..40) & (:posts[:published] == true) }
9
+ # SELECT "authors".* FROM "authors"
10
+ # INNER JOIN "posts" ON "posts"."author_id" = "authors"."id"
11
+ # WHERE "authors"."age" BETWEEN 20 AND 40 AND "posts"."published" = TRUE
12
+ ```
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
+ ## Reference
26
+
27
+ - {ActiveRecord::Refined::BlockSyntax BlockSyntax} — what a symbol answers to
28
+ inside a block: `as`, `asc`, `desc`, `collate`, `[]`, and through it the
29
+ conditions of {ActiveRecord::Refined::AST::Predications Predications} and
30
+ the operators of {ActiveRecord::Refined::AST::Arithmetics Arithmetics}.
31
+ - {ActiveRecord::Refined::BlockContext BlockContext} — what a block can call:
32
+ the aggregates, the scalar and window functions, `CASE`, `sql`, `fn`.
33
+ - {ActiveRecord::Refined::QueryMethods QueryMethods} — what the relation
34
+ takes: the block forms of `where` and the rest, the joins, `from_cte`,
35
+ `distinct_on`, `lateral`.
36
+ - {ActiveRecord::Refined::Writes Writes} — `update_all` and `upsert_all` with
37
+ a block.
38
+ - {ActiveRecord::Refined::Dialect Dialect} — one class per family of SQL
39
+ spellings, and `register` for an adapter of your own.
40
+
41
+ ## Guides
42
+
43
+ One topic at a time, each with the SQL it builds and where the adapters
44
+ differ:
45
+
46
+ - {file:docs/conditions.md Conditions} — comparisons, `LIKE`, `IN`, `NULL`,
47
+ `true?`, regular expressions, subqueries, `ANY` and `ALL`, arrays.
48
+ - {file:docs/joins.md Joins} — the `ON` as a block, table aliases, subqueries
49
+ and `LATERAL`.
50
+ - {file:docs/functions.md Aggregates and functions} — `count`, `filter`,
51
+ `string_agg`, the scalar functions, the clock, durations, `extract` and
52
+ `cast`.
53
+ - {file:docs/expressions.md Expressions} — arithmetic, the bitwise operators,
54
+ `CASE`.
55
+ - {file:docs/json.md JSON} — `dig`, `bury`, `except`, `key?`, `keys`, the
56
+ JSON aggregates, and where the adapters' JSON types part.
57
+ - {file:docs/windows.md Window functions} — `over`, `partition`, `order`,
58
+ frames.
59
+ - {file:docs/ordering.md Aliases, ordering and collation}
60
+ - {file:docs/grouping.md Grouping} — `DISTINCT ON`, `GROUPING SETS`,
61
+ `ROLLUP`, `CUBE`.
62
+ - {file:docs/ctes.md Common table expressions} — `with_recursive` and
63
+ `from_cte`.
64
+ - {file:docs/writing.md Writing} — `update_all` and `upsert_all`.
65
+ - {file:docs/time_zones.md Time zones} — what a block converts and what it
66
+ 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.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Activerecord
4
4
  module Refined
5
- VERSION = "0.10.0"
5
+ VERSION = "0.10.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-refined
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
@@ -233,22 +233,6 @@ dependencies:
233
233
  - ">="
234
234
  - !ruby/object:Gem::Version
235
235
  version: "0"
236
- - !ruby/object:Gem::Dependency
237
- name: yard-markdown-relative-links
238
- requirement: !ruby/object:Gem::Requirement
239
- requirements:
240
- -
241
- - ">="
242
- - !ruby/object:Gem::Version
243
- version: "0"
244
- type: :development
245
- prerelease: false
246
- version_requirements: !ruby/object:Gem::Requirement
247
- requirements:
248
- -
249
- - ">="
250
- - !ruby/object:Gem::Version
251
- version: "0"
252
236
  description: Adding clean and powerful query syntax on Active Record using refinements
253
237
  email:
254
238
  - shugo@ruby-lang.org
@@ -265,6 +249,7 @@ files:
265
249
  - docs/expressions.md
266
250
  - docs/functions.md
267
251
  - docs/grouping.md
252
+ - docs/index.md
268
253
  - docs/joins.md
269
254
  - docs/json.md
270
255
  - docs/ordering.md