briefly 0.2.1 → 0.3.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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +12 -6
- data/briefly.gemspec +1 -1
- data/lib/briefly/builder.rb +1 -1
- data/lib/briefly/rails/db.rb +5 -3
- data/lib/briefly/rescues.rb +1 -1
- data/lib/briefly/version.rb +1 -1
- data/lib/generators/briefly/install/templates/briefly.rb.tt +1 -0
- data/sig/briefly/builder.rbs +5 -5
- data/sig/briefly/rescues.rbs +3 -3
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 74e48b9af79f6a7c0b0f1ff2f601a7718e3a8071d314ee305aa36d0d43db7e77
|
|
4
|
+
data.tar.gz: fbdfadc26d3c27bd2c7393c1e03771313cf263911dbe392d712868219451d269
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d62e84d079845a5206409e9d5897b1ab55eadc64676791b07ce178a73135c440714b784439a960a434eb3c84aa355e9265ff0fde2a4ed98259364f9e03424d14
|
|
7
|
+
data.tar.gz: 40a3f01ecf038df93220d73df675c5e25422d0f483950100d976ecd9e8dc4bf5b01dca50db8fb7b750a2c19ff5b35d0074489bd46a850a991c18dc9cdc29beb0
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## v0.3.0 (2026-10-06)
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `App.db.value(sql, *binds)` returns the first column of the first row through `select_value`, or
|
|
13
|
+
`nil` for no row or SQL NULL. It uses the same positional and named binds as `select` and `query`.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **BREAKING:** the minimum supported Ruby is now 3.3. Ruby 3.2 reached end of life in March 2026, and the CI matrix no longer tests it.
|
|
17
|
+
|
|
9
18
|
## v0.2.1 (2026-07-31)
|
|
10
19
|
|
|
11
20
|
No change to `lib/` or `sig/`: this release carries the documentation the gem ships and the process
|
data/README.md
CHANGED
|
@@ -6,9 +6,9 @@ A terse, curated facade over your application's most reached-for objects.
|
|
|
6
6
|
[](https://github.com/svyatov/briefly/actions/workflows/main.yml)
|
|
7
7
|
[](https://app.codecov.io/gh/svyatov/briefly)
|
|
8
8
|
|
|
9
|
-
- **Ruby 3.
|
|
9
|
+
- **Ruby 3.3 and up.** Rails is optional: the gem does not declare it, and `Briefly::Rails` is
|
|
10
10
|
autoloaded only when you name it. The Rails packs themselves need Rails 7.2 or newer.
|
|
11
|
-
- **
|
|
11
|
+
- **26 shortcuts across six packs.** Config, env, view, db, instrument and reload, each usable on
|
|
12
12
|
its own or taken together through the `"rails"` umbrella.
|
|
13
13
|
- **One runtime dependency.** [candor](https://github.com/svyatov/candor), which is itself
|
|
14
14
|
dependency-free.
|
|
@@ -349,7 +349,7 @@ end
|
|
|
349
349
|
| `Briefly::Rails::Config` | `"rails/config"` | `config`, `config_x`, `root`, `cache`, `logger`, `credentials`, `error`, `config_for` |
|
|
350
350
|
| `Briefly::Rails::Env` | `"rails/env"` | `env` and its predicates |
|
|
351
351
|
| `Briefly::Rails::View` | `"rails/view"` | `helpers`, `routes`, `renderer`, `render` |
|
|
352
|
-
| `Briefly::Rails::DB` | `"rails/db"` | `connection`, `transaction`, `select`, `query`, `connected_to`, `reading`, `writing` |
|
|
352
|
+
| `Briefly::Rails::DB` | `"rails/db"` | `connection`, `transaction`, `select`, `value`, `query`, `connected_to`, `reading`, `writing` |
|
|
353
353
|
| `Briefly::Rails::Instrument` | `"rails/instrument"` | `instrument` |
|
|
354
354
|
| `Briefly::Rails::Reload` | `"rails/reload"` | none; clears memos on every code reload |
|
|
355
355
|
|
|
@@ -370,6 +370,7 @@ end
|
|
|
370
370
|
| `connection` | `conn` | forwards keywords and the block to `base.with_connection`, yielding the connection and auto-releasing |
|
|
371
371
|
| `transaction` | `txn` | forwards keywords and the block to `base.transaction` |
|
|
372
372
|
| `select` | | `base.with_connection { \|c\| c.select_all(sql) }`, a read (SELECT) on the cache-aware path |
|
|
373
|
+
| `value` | | `base.with_connection { \|c\| c.select_value(sql) }`, the first column of the first row, or `nil` for no row or SQL NULL |
|
|
373
374
|
| `query` | | `base.with_connection { \|c\| c.exec_query(sql) }`, arbitrary SQL, writes and DDL included |
|
|
374
375
|
| `connected_to` | | forwards every argument to `base.connected_to` (`role:`, `shard:`, `prevent_writes:`) |
|
|
375
376
|
| `reading` | | runs the block under the `:reading` role |
|
|
@@ -384,22 +385,27 @@ App = Briefly.define do
|
|
|
384
385
|
end
|
|
385
386
|
|
|
386
387
|
App.db.txn { App.db.select("select * from users where id = ?", 1) }
|
|
387
|
-
App.db.
|
|
388
|
+
App.db.value("select count(*) from users")
|
|
388
389
|
```
|
|
389
390
|
|
|
390
|
-
`select` and `query` are the
|
|
391
|
+
`select`, `value` and `query` are the raw-SQL helpers, differing only in which adapter path they take.
|
|
391
392
|
`select(sql, *binds)` runs a read through `select_all`, the path Rails recommends for a raw SELECT,
|
|
392
393
|
returning an `ActiveRecord::Result` without clearing the query cache. `query(sql, *binds)` runs
|
|
393
394
|
arbitrary SQL through `exec_query`: reads, writes, and DDL all execute. The name tells you which
|
|
394
395
|
you're reaching for; neither polices the SQL it's handed, so `select` will happily run a write you
|
|
395
396
|
give it. The split is name and cache-path, not a runtime guard.
|
|
396
397
|
|
|
397
|
-
|
|
398
|
+
`value(sql, *binds)` reads through `select_value` when you need a single result, such as a count.
|
|
399
|
+
It returns the first column of the first row, or `nil` for no row or SQL NULL. Its arguments follow
|
|
400
|
+
the same bind convention as `select` and `query`, rather than the adapter's full signature.
|
|
401
|
+
|
|
402
|
+
All three sanitize through `base.sanitize_sql_array` when binds are given, and pass the statement through
|
|
398
403
|
untouched when they are not. Positional and named binds both work, from application code:
|
|
399
404
|
|
|
400
405
|
```ruby
|
|
401
406
|
App.db.select("select * from users where name like '%ada%'") # no binds, passed through
|
|
402
407
|
App.db.select("select * from users where id = ?", 123) # positional
|
|
408
|
+
App.db.value("select count(*) from users where active = :active", active: true)
|
|
403
409
|
App.db.query("update users set active = true where id = :id", id: 1) # named, a write via `query`
|
|
404
410
|
```
|
|
405
411
|
|
data/briefly.gemspec
CHANGED
|
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
|
|
|
15
15
|
spec.homepage = "https://github.com/svyatov/briefly"
|
|
16
16
|
spec.license = "MIT"
|
|
17
17
|
|
|
18
|
-
spec.required_ruby_version = ">= 3.
|
|
18
|
+
spec.required_ruby_version = ">= 3.3.0"
|
|
19
19
|
|
|
20
20
|
spec.require_paths = ["lib"]
|
|
21
21
|
spec.files = Dir["lib/**/*.{rb,tt}"] + Dir["sig/**/*"] +
|
data/lib/briefly/builder.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Briefly
|
|
|
9
9
|
# A validated {#compile!} pass, ready for a {Briefly::Facade} to install. +child_plans+ holds one
|
|
10
10
|
# +[child_facade, child_plan]+ pair per namespace this pass collected, so +__commit+ can walk the
|
|
11
11
|
# tree children first.
|
|
12
|
-
Plan =
|
|
12
|
+
Plan = Data.define(:defs, :rescue_entries, :children, :child_plans)
|
|
13
13
|
|
|
14
14
|
# @return [Briefly::Facade] the facade under construction, for packs that need lifecycle hooks
|
|
15
15
|
attr_reader :facade
|
data/lib/briefly/rails/db.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Briefly
|
|
|
16
16
|
# the captured class would go stale on the first code reload — permanently, since {Reload} clears
|
|
17
17
|
# memos, not closures. A Module is accepted for applications outside the autoloader, with that caveat.
|
|
18
18
|
#
|
|
19
|
-
# Nothing here memoizes: the block-form shortcuts can't be memoized, and +select+/+query+ take
|
|
19
|
+
# Nothing here memoizes: the block-form shortcuts can't be memoized, and +select+/+value+/+query+ take
|
|
20
20
|
# arguments. So the pack does not wire {Reload}, and works without a booted application.
|
|
21
21
|
#
|
|
22
22
|
# +connected_to+ forwards every argument to +base.connected_to+ — +role:+, +shard:+, +prevent_writes:+,
|
|
@@ -48,11 +48,12 @@ module Briefly
|
|
|
48
48
|
# always reads, while `shard:` and `prevent_writes:` still forward.
|
|
49
49
|
builder.shortcut(:reading) { |**opts, &blk| connected_to(**opts, role: :reading, &blk) }
|
|
50
50
|
builder.shortcut(:writing) { |**opts, &blk| connected_to(**opts, role: :writing, &blk) }
|
|
51
|
-
# `select` and `query` share one closure, differing only by the adapter method. `select` runs a
|
|
51
|
+
# `select`, `value` and `query` share one closure, differing only by the adapter method. `select` runs a
|
|
52
52
|
# read through `select_all` — the read-optimized path for a raw SELECT, returning an
|
|
53
53
|
# `ActiveRecord::Result` without clearing the query cache. `query` runs arbitrary SQL through
|
|
54
54
|
# `exec_query` — writes and DDL included. Neither polices the SQL it's given; the split is name
|
|
55
55
|
# and cache-path, not a runtime read/write guard.
|
|
56
|
+
# `value` uses `select_value` for the first column of the first row, or nil for no row or SQL NULL.
|
|
56
57
|
#
|
|
57
58
|
# `with_connection`, not `connection`: the latter is soft-deprecated, and raises outright under
|
|
58
59
|
# `ActiveRecord.permanent_connection_checkout = :disallowed`.
|
|
@@ -60,7 +61,7 @@ module Briefly
|
|
|
60
61
|
# A bindless statement must skip `sanitize_sql_array`, which would fall through to its
|
|
61
62
|
# `statement % values` branch and raise on any literal `%` — `... like '%foo%'`.
|
|
62
63
|
#
|
|
63
|
-
# No `**` parameter on
|
|
64
|
+
# No `**` parameter on these shortcuts, deliberately: taking no keywords is what makes Ruby pack
|
|
64
65
|
# `select(sql, id: 1)` into `binds` as a trailing Hash, which is how named binds reach
|
|
65
66
|
# `sanitize_sql_array`. A `**opts` would swallow them and send the statement unbound.
|
|
66
67
|
run = lambda do |method, sql, binds|
|
|
@@ -69,6 +70,7 @@ module Briefly
|
|
|
69
70
|
record.with_connection { |connection| connection.public_send(method, statement) }
|
|
70
71
|
end
|
|
71
72
|
builder.shortcut(:select) { |sql, *binds| run.call(:select_all, sql, binds) }
|
|
73
|
+
builder.shortcut(:value) { |sql, *binds| run.call(:select_value, sql, binds) }
|
|
72
74
|
builder.shortcut(:query) { |sql, *binds| run.call(:exec_query, sql, binds) }
|
|
73
75
|
builder
|
|
74
76
|
end
|
data/lib/briefly/rescues.rb
CHANGED
data/lib/briefly/version.rb
CHANGED
|
@@ -39,6 +39,7 @@ end
|
|
|
39
39
|
# db <%= name %>.db.connection { |c| }, <%= name %>.db.conn { |c| } -> yields a connection, auto-releases
|
|
40
40
|
# <%= name %>.db.transaction { }, <%= name %>.db.txn { } -> wraps the block in a transaction
|
|
41
41
|
# <%= name %>.db.select(sql, id: 1) / select(sql, 1) -> a read (SELECT); named or positional binds
|
|
42
|
+
# <%= name %>.db.value(sql, id: 1) / value(sql, 1) -> first column of first row; nil for no row or SQL NULL
|
|
42
43
|
# <%= name %>.db.query(sql, id: 1) / query(sql, 1) -> arbitrary SQL (writes, DDL); same binds
|
|
43
44
|
# <%= name %>.db.connected_to(role:, shard:, prevent_writes:) { }
|
|
44
45
|
# <%= name %>.db.reading { }, <%= name %>.db.writing { } -> run the block under a role
|
data/sig/briefly/builder.rbs
CHANGED
|
@@ -2,11 +2,11 @@ module Briefly
|
|
|
2
2
|
# Receives the DSL. Blocks are `instance_eval`'d here, never on the facade.
|
|
3
3
|
class Builder
|
|
4
4
|
# A validated `compile!` pass, ready for a `Facade` to install.
|
|
5
|
-
class Plan < ::
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
class Plan < ::Data
|
|
6
|
+
attr_reader defs: Hash[Symbol, Shortcut]
|
|
7
|
+
attr_reader rescue_entries: Array[Briefly::rescue_entry]
|
|
8
|
+
attr_reader children: Hash[Symbol, Facade]
|
|
9
|
+
attr_reader child_plans: Array[[ Facade, Plan ]]
|
|
10
10
|
|
|
11
11
|
def self.new: (Hash[Symbol, Shortcut] defs, Array[Briefly::rescue_entry] rescue_entries, Hash[Symbol, Facade] children, Array[[ Facade, Plan ]] child_plans) -> Plan
|
|
12
12
|
end
|
data/sig/briefly/rescues.rbs
CHANGED
|
@@ -2,9 +2,9 @@ module Briefly
|
|
|
2
2
|
# An ordered list of facade-wide and global `rescue_from` registrations, queried at dispatch time.
|
|
3
3
|
class Rescues
|
|
4
4
|
# A single registration.
|
|
5
|
-
class Entry < ::
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
class Entry < ::Data
|
|
6
|
+
attr_reader klass: Class
|
|
7
|
+
attr_reader handler: Briefly::handler
|
|
8
8
|
|
|
9
9
|
def self.new: (Class klass, Briefly::handler handler) -> Entry
|
|
10
10
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: briefly
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leonid Svyatov
|
|
@@ -77,14 +77,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
77
77
|
requirements:
|
|
78
78
|
- - ">="
|
|
79
79
|
- !ruby/object:Gem::Version
|
|
80
|
-
version: 3.
|
|
80
|
+
version: 3.3.0
|
|
81
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
82
|
requirements:
|
|
83
83
|
- - ">="
|
|
84
84
|
- !ruby/object:Gem::Version
|
|
85
85
|
version: '0'
|
|
86
86
|
requirements: []
|
|
87
|
-
rubygems_version: 4.0.
|
|
87
|
+
rubygems_version: 4.0.20
|
|
88
88
|
specification_version: 4
|
|
89
89
|
summary: A terse, curated facade over your application's most reached-for objects.
|
|
90
90
|
test_files: []
|