briefly 0.2.0 → 0.2.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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -22
  3. data/README.md +106 -56
  4. data/lib/briefly/version.rb +1 -1
  5. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2caa5442969820b1605dee751c3e544987d5edc7d89e701ef7a66b222ba1e20
4
- data.tar.gz: b4aa66328b61c6fe4e56f9b7b4fec9798ad396fd486b3957152aecb227aa7244
3
+ metadata.gz: f6c561612c29ff83aaaccdf483e6e563633e54d3dc81c92856857db2f26978b3
4
+ data.tar.gz: a257e3be54bb8e1bb076009dc5982a7c15a6add60f16225bb774e9e3100f3db4
5
5
  SHA512:
6
- metadata.gz: ebe66d5820348b8a635abd9f2870458aca11a905c6718399001099ae60e7a340b75c5f0af0f5cb096f7f0482ef7ddb99b7856d3efefdf9437cd4401fdead04a2
7
- data.tar.gz: 8b3b5ecd63e4b14ca45d614f55214d2fd22abadb107d57e320758058d7d402819e08e8e0614548fd6805646dfecb6e0dbf60f5cb9d6d2dc023b1620452d9951b
6
+ metadata.gz: 4496b43e5d42fb6b90d0b8f528092207ff867dfbab5febe1d36b69451c307527bd1a056da118a20a48770b77601c49c8965018fa76c904fc203642a4bd5dc178
7
+ data.tar.gz: 430188265df3b33c124f90cbf28de0b34f27fa3090174e74d60a94a63d32a97a4bb905bfb46a72e50263977a44c89cecd29a66799f25847e520ee268071ea18e
data/CHANGELOG.md CHANGED
@@ -6,38 +6,60 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## v0.2.1 (2026-07-31)
10
+
11
+ No change to `lib/` or `sig/`: this release carries the documentation the gem ships and the process
12
+ that publishes it. `Briefly::VERSION` is the only Ruby file that differs from v0.2.0.
13
+
14
+ ### Added
15
+ - The README declares the public API that Semantic Versioning covers, and what falls outside it, so
16
+ a MINOR bump before `1.0.0` now has a stated scope rather than an implied one.
17
+ - CONTRIBUTING.md states the deprecation policy: a public item ships deprecated in one release, with
18
+ a runtime warning naming its replacement and the earliest removal version, before a later release
19
+ removes it. v0.2.0's three removals predate the policy and are named there.
20
+
21
+ ### Changed
22
+ - Development now resolves through committed lockfiles. `gemfiles/` holds one file per Rails line,
23
+ each pinning that line and evaluating the root `Gemfile`, and 7.2, 8.0 and 8.1 commit a lockfile
24
+ beside theirs; CI installs from them frozen instead of deleting `Gemfile.lock` and re-resolving.
25
+ Contributors testing another Rails version now pass `BUNDLE_GEMFILE` rather than `RAILS_VERSION`.
26
+ Nothing an application installs changed.
27
+ - Releases are published by CI from a pushed tag, authenticating to RubyGems through OIDC, gated on
28
+ a manual approval, and signed with sigstore. No publishing credential exists on any machine.
29
+ `bundle exec rake release` no longer publishes; it prints the tag steps and exits.
30
+
9
31
  ## v0.2.0 (2026-07-14)
10
32
 
11
33
  ### Added
12
- - `rails g briefly:install` a Rails generator that writes `config/initializers/briefly.rb`: a
34
+ - `rails g briefly:install`, a Rails generator that writes `config/initializers/briefly.rb`: a
13
35
  working `App` facade plus a commented, concern-grouped map of every shortcut `use "rails"` installs.
14
36
  Pass a name (`rails g briefly:install Facade`) to rename the constant; re-run it after upgrading to
15
37
  refresh the map, as Rails prompts before overwriting. It loads only under `rails generate`, so no
16
38
  Rails runtime dependency is added.
17
39
  - `Briefly::Rails::Config` gains `error` (`Rails.error`, the framework's handled-error reporter) and
18
40
  `config_for` (per-environment YAML via `Rails.application.config_for`). Both are live lookups;
19
- `config_for` takes an argument, so it never memoizes compose one that does by chaining `.memoize`
41
+ `config_for` takes an argument, so it never memoizes. Compose one that does by chaining `.memoize`
20
42
  onto a shortcut: `shortcut(:x) { config_for(:x) }.memoize`.
21
43
  - `Briefly::Rails::DB` gains `connected_to`, `reading` and `writing` for multi-database routing.
22
44
  `connected_to` forwards the full Rails surface (`role:`, `shard:`, `prevent_writes:`, custom roles);
23
45
  `reading`/`writing` are sugar that pin their role and forward the rest. `base` must be
24
46
  `ActiveRecord::Base` or an abstract connection class; a concrete model raises `NotImplementedError`.
25
- - `Briefly::Rails::DB` gains `select` a raw-SQL read through `select_all`, returning an
47
+ - `Briefly::Rails::DB` gains `select`, a raw-SQL read through `select_all`, returning an
26
48
  `ActiveRecord::Result` on the query-cache-preserving path, with the same positional and named
27
49
  bind-safety as `query`. `query` keeps running arbitrary SQL (writes and DDL included) via `exec_query`.
28
50
  - `Briefly::Rails::Env` gains `dev?` and `prod?`, aliases of `development?` and `production?`.
29
- - `Briefly::Rails::Instrument` a new `"rails/instrument"` pack with one `instrument` shortcut over
51
+ - `Briefly::Rails::Instrument`, a new `"rails/instrument"` pack with one `instrument` shortcut over
30
52
  `ActiveSupport::Notifications.instrument(name, payload) { }`. Usable on its own; `use "rails"`
31
53
  includes it, so `App.instrument` comes for free.
32
54
 
33
55
  ### Changed
34
- - **BREAKING:** Facade management moved behind a single `App.briefly` accessor
56
+ - **BREAKING:** Facade management moved behind a single `App.briefly` accessor:
35
57
  `App.briefly.configure`, `App.briefly.shortcuts`, `App.briefly.shortcut?` and
36
58
  `App.briefly.clear_memos!`. This frees `configure`, `shortcuts`, `shortcut?` and `clear_memos!` for
37
59
  use as your own shortcut names; only `briefly`, `inspect` and `to_s` stay reserved on the facade's
38
60
  public surface.
39
61
  - **BREAKING:** `Briefly::Rails::DB#connection`/`#conn` is now an auto-releasing block that forwards to
40
- `base.with_connection`, yielding the leased connection and releasing it at block exit the shape of
62
+ `base.with_connection`, yielding the leased connection and releasing it at block exit, the shape of
41
63
  `transaction`. It requires a block; the old bare `base.lease_connection` accessor (held on the thread
42
64
  and never released, a leak outside a request) is gone, with no compatibility shim. Anyone needing a
43
65
  held raw lease calls `lease_connection` on their model directly.
@@ -45,8 +67,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
45
67
  calls are verified rather than mocked. `activerecord` and `sqlite3` join `activesupport` as dev-only
46
68
  dependencies; the gem still declares no Rails runtime dependency.
47
69
  - **BREAKING:** `shortcut` now returns the `Briefly::Shortcut` it declares instead of the canonical
48
- name Symbol. Refine it in place `shortcut(:x) { ... }.memoize`, `.rescue_from(Error) { fallback }`,
49
- in any order so a shortcut's name is never written twice to annotate it. A bodiless `shortcut(:x)`
70
+ name Symbol. Refine it in place (`shortcut(:x) { ... }.memoize`, `.rescue_from(Error) { fallback }`,
71
+ in any order) so a shortcut's name is never written twice to annotate it. A bodiless `shortcut(:x)`
50
72
  fetches an already-declared shortcut (canonical or alias) to refine, raising
51
73
  `Briefly::UnknownShortcutError` on an unknown name; it never re-declares. A shortcut's memoization
52
74
  and its own error handlers live on the shortcut itself, so refining it after a redeclaration affects
@@ -56,51 +78,51 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
56
78
  Scope a handler to a shortcut by chaining `.rescue_from` onto it. Global `Briefly.rescue_from` is
57
79
  unchanged.
58
80
  - **BREAKING:** the rescue-handler registry is now `Briefly::Rescues` (was
59
- `Briefly::ErrorRegistry`), reached through `Briefly.rescues` (was `Briefly.errors`) it holds
81
+ `Briefly::ErrorRegistry`), reached through `Briefly.rescues` (was `Briefly.errors`), because it holds
60
82
  `rescue_from` handlers, not errors. `Briefly.rescues.clear` still resets globally registered
61
83
  handlers; the internal `#wide` enumerator is gone, replaced by `#size` for counting registrations.
62
84
 
63
85
  ### Removed
64
- - **BREAKING:** `App.reset!` use `App.briefly.clear_memos!`. It was a pure alias for `clear_memos!`
86
+ - **BREAKING:** `App.reset!`. Use `App.briefly.clear_memos!`. It was a pure alias for `clear_memos!`
65
87
  with no internal callers.
66
- - **BREAKING:** The top-level `memoize` DSL verb chain `.memoize` onto the shortcut `shortcut`
88
+ - **BREAKING:** The top-level `memoize` DSL verb. Chain `.memoize` onto the shortcut `shortcut`
67
89
  returns (`shortcut(:x) { ... }.memoize`), or `shortcut(:x).memoize` for one declared elsewhere.
68
- - **BREAKING:** Scoping `rescue_from` by shortcut name on the top-level verb both single-name
90
+ - **BREAKING:** Scoping `rescue_from` by shortcut name on the top-level verb, both single-name
69
91
  `rescue_from(Error, :x)` and multi-name `rescue_from(Error, :a, :b)`. Scope on the shortcut instead:
70
92
  `shortcut(:x).rescue_from(Error) { ... }`, chaining onto each of several.
71
93
 
72
94
  ## v0.1.0 (2026-07-10)
73
95
 
74
96
  ### Added
75
- - `Briefly.define` builds a facade of real, introspectable, stubbable methods no `method_missing`.
97
+ - `Briefly.define` builds a facade of real, introspectable, stubbable methods, with no `method_missing`.
76
98
  - `shortcut(canonical, *aliases, &body)` with argument, keyword and block forwarding; predicate and
77
99
  bang names; last-declaration-wins overrides. Each shortcut compiles to a real method carrying its
78
- body's `arity` and parameter kinds, with a `source_location` pointing at the block you declared
79
- keyword names are exact, positionals get generated ones (`__p0`, `__r1`, …). A wrong-arity call, a
100
+ body's `arity` and parameter kinds, with a `source_location` pointing at the block you declared.
101
+ Keyword names are exact, positionals get generated ones (`__p0`, `__r1`, …). A wrong-arity call, a
80
102
  missing required keyword and an unknown keyword raise `ArgumentError` at the call site, before any
81
103
  `rescue_from` handler is consulted.
82
- - `namespace(name) { ... }` groups shortcuts behind a child facade, so `App.db.query` works without
104
+ - `namespace(name) { ... }` groups shortcuts behind a child facade, so `App.db.query` works without
83
105
  burning root-level names. Takes the whole DSL, including nested namespaces. `clear_memos!` cascades
84
106
  into it. `configure` stays atomic across the whole tree: a pass that raises anywhere leaves the root
85
107
  and every namespace untouched. A `shortcut` of the same name overrides a namespace and drops the child.
86
- - `memoize` permanent per-process caching, correct for `nil`/`false`, never caching a rescued fallback.
87
- - `rescue_from` facade-scoped, facade-wide and global (`Briefly.rescue_from`) handlers whose return
108
+ - `memoize`, permanent per-process caching, correct for `nil`/`false`, never caching a rescued fallback.
109
+ - `rescue_from`, with facade-scoped, facade-wide and global (`Briefly.rescue_from`) handlers whose return
88
110
  value becomes the shortcut's value.
89
111
  - `clear_memos!` / `reset!`, `configure`, `shortcuts`, `shortcut?`, `inspect`.
90
112
  - Pack protocol: any object responding to `#install(builder, **opts)`. `use` accepts keywords,
91
113
  forwarded to the pack's `install`. `Briefly.register(name, pack)` / `Briefly.pack(name)` provide a
92
114
  pack registry, so `use "rails/db"` resolves; values may be a pack or a constant path resolved on
93
115
  first use, and an unknown name raises `Briefly::UnknownPackError`.
94
- - Method fabrication the arity, `parameters` and `source_location` each shortcut reports is
116
+ - Method fabrication, meaning the arity, `parameters` and `source_location` each shortcut reports, is
95
117
  provided by [candor](https://github.com/svyatov/candor), the sole runtime dependency (candor itself
96
118
  has none). Compiled bodies live under `Candor::BODY_PREFIX`; a shortcut may not take a name there.
97
- - `Briefly::Rails` `config`/`c`, `config_x`/`x`, `env` and its predicates, `root`, `cache`,
119
+ - `Briefly::Rails`, with `config`/`c`, `config_x`/`x`, `env` and its predicates, `root`, `cache`,
98
120
  `logger`/`log`, `credentials`/`cred`, `helpers`/`h`, `routes`/`r`, `renderer`, `render`, plus a `db`
99
121
  namespace. Nothing in the pack is memoized. Its `Config`, `Env` and `View` groups are usable alone.
100
- - `Briefly::Rails::DB` `connection`/`conn`, `transaction`/`txn`, `query`. `query` takes positional
122
+ - `Briefly::Rails::DB`, with `connection`/`conn`, `transaction`/`txn`, `query`. `query` takes positional
101
123
  (`query(sql, 1)`) or named (`query(sql, id: 1)`) binds, and passes a bindless statement through
102
124
  unsanitized. Takes `base:` (default `"ApplicationRecord"`), resolved on every call so a reloaded
103
125
  class is never captured. Memoizes nothing and wires no lifecycle hook, so it works without a booted
104
126
  application.
105
- - `Briefly::Rails::Reload` clears memos via `Rails.application.reloader.to_prepare`.
127
+ - `Briefly::Rails::Reload` clears memos via `Rails.application.reloader.to_prepare`.
106
128
  - RBS signatures in `sig/`.
data/README.md CHANGED
@@ -1,18 +1,25 @@
1
- # briefly   [![Gem Version](https://badge.fury.io/rb/briefly.svg)](https://rubygems.org/gems/briefly) [![CI](https://github.com/svyatov/briefly/actions/workflows/main.yml/badge.svg)](https://github.com/svyatov/briefly/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/svyatov/briefly/branch/main/graph/badge.svg)](https://codecov.io/gh/svyatov/briefly) [![Documentation](https://img.shields.io/badge/docs-rubydoc.info-blue.svg)](https://rubydoc.info/gems/briefly) [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.2-CC342D.svg)](https://www.ruby-lang.org) [![Types: RBS](https://img.shields.io/badge/types-RBS-8A2BE2.svg)](https://github.com/svyatov/briefly/tree/main/sig)
1
+ # briefly
2
2
 
3
- A terse, curated facade over your application's most reached-for objects. Thread-safe, reload-correct,
4
- with a Rails pack included.
3
+ A terse, curated facade over your application's most reached-for objects.
5
4
 
6
- Every app grows an `App` module full of `def self.config = Rails.configuration`. `briefly` gives
7
- you that module without writing it, as real methods. There is no `method_missing`, so `respond_to?`,
8
- console tab-completion and test stubbing all work. Each shortcut carries its body's `arity` and
9
- parameter kinds (keyword names are exact, positionals get generated ones), and its `source_location`
10
- is the block you declared, so jump-to-definition lands in your initializer rather than inside the gem.
11
- That fabrication is [candor](https://github.com/svyatov/candor), extracted from this gem and its only
12
- runtime dependency; candor itself has none.
5
+ [![gem](https://img.shields.io/gem/v/briefly)](https://rubygems.org/gems/briefly)
6
+ [![CI](https://github.com/svyatov/briefly/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/svyatov/briefly/actions/workflows/main.yml)
7
+ [![coverage](https://codecov.io/gh/svyatov/briefly/branch/main/graph/badge.svg)](https://app.codecov.io/gh/svyatov/briefly)
8
+
9
+ - **Ruby 3.2 and up.** Rails is optional: the gem does not declare it, and `Briefly::Rails` is
10
+ autoloaded only when you name it. The Rails packs themselves need Rails 7.2 or newer.
11
+ - **25 shortcuts across six packs.** Config, env, view, db, instrument and reload, each usable on
12
+ its own or taken together through the `"rails"` umbrella.
13
+ - **One runtime dependency.** [candor](https://github.com/svyatov/candor), which is itself
14
+ dependency-free.
15
+
16
+ ```bash
17
+ bundle add briefly
18
+ ```
19
+
20
+ Then, in `config/initializers/app.rb`:
13
21
 
14
22
  ```ruby
15
- # config/initializers/app.rb
16
23
  App = Briefly.define do
17
24
  use "rails"
18
25
  shortcut(:redis) { REDIS_POOL }
@@ -25,16 +32,16 @@ App.redis # => REDIS_POOL
25
32
  App.local? # => true in development and test
26
33
  ```
27
34
 
28
- ## Installation
29
-
30
- ```ruby
31
- gem "briefly"
32
- ```
35
+ Every app grows an `App` module full of `def self.config = Rails.configuration`. `briefly` gives
36
+ you that module without writing it, as real methods. There is no `method_missing`, so `respond_to?`,
37
+ console tab-completion and test stubbing all work. Each shortcut carries its body's `arity` and
38
+ parameter kinds (keyword names are exact, positionals get generated ones), and its `source_location`
39
+ is the block you declared, so jump-to-definition lands in your initializer rather than inside the gem.
40
+ That fabrication is [candor](https://github.com/svyatov/candor), extracted from this gem.
33
41
 
34
- Ruby >= 3.2. The one runtime dependency is `candor`. Rails is optional: the gem does not declare
35
- it, and `Briefly::Rails` is autoloaded only when you name it.
42
+ ## The Rails generator
36
43
 
37
- In a Rails app, `rails g briefly:install` writes `config/initializers/briefly.rb` a working `App`
44
+ In a Rails app, `rails g briefly:install` writes `config/initializers/briefly.rb`: a working `App`
38
45
  facade plus a commented, concern-grouped map of every shortcut the `rails` pack gives you. Pass a
39
46
  name (`rails g briefly:install Facade`) to call the constant something else. Re-run it after upgrading
40
47
  to refresh the map; Rails prompts before overwriting. The generator loads only under `rails generate`,
@@ -43,7 +50,8 @@ so it adds no runtime dependency.
43
50
  ## Core concepts
44
51
 
45
52
  A **facade** is the object `Briefly.define` returns. You assign it to a constant of your choosing;
46
- `briefly` never installs one for you. Multiple independent facades share no state:
53
+ `briefly` never installs one for you. Multiple independent facades share no state, so an initializer
54
+ can declare as many as it needs:
47
55
 
48
56
  ```ruby
49
57
  App = Briefly.define { use "rails" }
@@ -51,7 +59,8 @@ Admin = Briefly.define { shortcut(:audit_log) { AuditLog } }
51
59
  ```
52
60
 
53
61
  A **shortcut** is a name plus a body. The body is always attached to `shortcut`, one block in one
54
- place, and runs bound to the facade, so it can reach the facade's other shortcuts:
62
+ place, and runs bound to the facade, so it can reach the facade's other shortcuts. Inside the
63
+ initializer's `Briefly.define` block:
55
64
 
56
65
  ```ruby
57
66
  Briefly.define do
@@ -67,8 +76,8 @@ overrides it silently; that is how you override a pack's shortcut.
67
76
 
68
77
  ## `memoize`
69
78
 
70
- `shortcut` returns the shortcut you refine. Chain `.memoize` onto it to cache the value — computed once,
71
- then reused for the process lifetime:
79
+ `shortcut` returns the shortcut you refine. Chain `.memoize` onto it in your initializer to cache the
80
+ value, computed once, then reused for the process lifetime:
72
81
 
73
82
  ```ruby
74
83
  Briefly.define do
@@ -76,8 +85,9 @@ Briefly.define do
76
85
  end
77
86
  ```
78
87
 
79
- To memoize a shortcut you didn't declare here one a pack installed, say call `shortcut(name)`
80
- with no block to fetch it, then chain onto that. A bodiless `shortcut` never re-declares:
88
+ To memoize a shortcut you didn't declare yourself, one a pack installed, say, call `shortcut(name)`
89
+ with no block to fetch it, then chain onto that. A bodiless `shortcut` never re-declares, so this
90
+ also belongs in the initializer:
81
91
 
82
92
  ```ruby
83
93
  Briefly.define do
@@ -95,7 +105,8 @@ retried on next call.
95
105
 
96
106
  That guarantee is per-cell, and does not compose. A memoized shortcut whose body *reads* a
97
107
  rescue-backed shortcut succeeds, so its own value (containing the fallback) is cached for the
98
- process lifetime, even after the inner shortcut recovers:
108
+ process lifetime, even after the inner shortcut recovers. Two initializer declarations that trap
109
+ you:
99
110
 
100
111
  ```ruby
101
112
  shortcut(:flaky) { external_call }.rescue_from(SomeError) { "unknown" }.memoize
@@ -103,13 +114,14 @@ shortcut(:summary) { "build #{flaky}" }.memoize # <- caches "build unknown" fo
103
114
  ```
104
115
 
105
116
  Clearing is a neutral primitive. Management lives behind one accessor, `App.briefly`, so names like
106
- `configure`, `shortcuts`, `shortcut?` and `clear_memos!` stay yours to use as shortcuts:
117
+ `configure`, `shortcuts`, `shortcut?` and `clear_memos!` stay yours to use as shortcuts. Call it
118
+ from application code:
107
119
 
108
120
  ```ruby
109
121
  App.briefly.clear_memos! # => App (thread-safe)
110
122
  ```
111
123
 
112
- Reclaim one of those names and `App.configure` calls *your* shortcut the old management call now
124
+ Reclaim one of those names and `App.configure` calls *your* shortcut; the old management call now
113
125
  lives only at `App.briefly.configure`. Worth knowing when porting pre-0.2.0 code: a leftover
114
126
  `App.configure { ... }` won't raise if a `configure` shortcut exists, it just runs the shortcut and,
115
127
  like any non-yielding method, drops the block.
@@ -121,7 +133,7 @@ like any non-yielding method, drops the block.
121
133
  The handler's return value becomes the shortcut's return value. To guard one shortcut, chain
122
134
  `.rescue_from(error_class) { |e, name| ... }` onto it. The top-level `rescue_from` verb is for what a
123
135
  single shortcut can't voice: a facade-wide handler, consulted after each shortcut's own. Error class
124
- first, always:
136
+ first, always. Both forms belong in the initializer:
125
137
 
126
138
  ```ruby
127
139
  Briefly.define do
@@ -131,7 +143,7 @@ Briefly.define do
131
143
  end
132
144
  ```
133
145
 
134
- The top-level `rescue_from` takes no shortcut names pass any and it raises `ArgumentError`, pointing
146
+ The top-level `rescue_from` takes no shortcut names. Pass any and it raises `ArgumentError`, pointing
135
147
  you at `shortcut(name).rescue_from(...)`. Scoping a handler to a shortcut lives in one channel, the
136
148
  shortcut itself, so its name is never written twice to annotate it. To share one handler across a few
137
149
  shortcuts, chain `.rescue_from` onto each; to cover them all, register it facade-wide.
@@ -140,9 +152,10 @@ Unlike a shortcut body, a handler is not bound to the facade. It is called as
140
152
  `handler.call(error, name)`, so `self` stays whatever it was where you wrote the block. Reach for
141
153
  constants (`Rails.logger`, `Sentry`) rather than bare shortcut names inside a handler.
142
154
 
143
- > **A facade-wide `rescue_from(StandardError)` catches your own bugs, not just your app's.**
155
+ > [!WARNING]
156
+ > A facade-wide `rescue_from(StandardError)` catches your own bugs, not just your app's.
144
157
  > `briefly` cannot tell an error raised *by* a shortcut body from one raised *about* the call; a
145
- > typo and a dead Redis both arrive as a `StandardError`:
158
+ > typo and a dead Redis both arrive as a `StandardError`. Written in an initializer:
146
159
  >
147
160
  > ```ruby
148
161
  > shortcut(:host) { Rails.aplication.config.host } # typo -> NoMethodError
@@ -160,8 +173,10 @@ constants (`Rails.logger`, `Sentry`) rather than bare shortcut names inside a ha
160
173
  > body calling another with a bad argument list is a different matter: that raises *inside* the
161
174
  > calling body, where the calling shortcut's own handler sees it like any other error.
162
175
 
163
- > **⚠️ `{}` needs parentheses (standalone `rescue_from` only).** `rescue_from StandardError { ... }`
164
- > binds the block to `StandardError`, not to `rescue_from`, and raises `NoMethodError`. Use either form:
176
+ > [!IMPORTANT]
177
+ > `{}` needs parentheses, on the standalone `rescue_from` only. `rescue_from StandardError { ... }`
178
+ > binds the block to `StandardError`, not to `rescue_from`, and raises `NoMethodError`. Use either
179
+ > form inside your `Briefly.define` block:
165
180
  >
166
181
  > ```ruby
167
182
  > rescue_from StandardError do |e| ... end # do/end, no parens
@@ -188,11 +203,12 @@ For a raised error, the first `is_a?` match wins, searching in this order:
188
203
  | 2 | Facade-wide handlers | last registered first |
189
204
  | 3 | Global handlers (`Briefly.rescue_from`) | last registered first |
190
205
 
191
- No match the error propagates.
206
+ With no match, the error propagates.
192
207
 
193
208
  ## `namespace`
194
209
 
195
- `namespace` groups shortcuts behind a name, so the root keyspace stays yours:
210
+ `namespace` groups shortcuts behind a name, so the root keyspace stays yours. In the initializer,
211
+ and then anywhere in the app:
196
212
 
197
213
  ```ruby
198
214
  App = Briefly.define do
@@ -228,7 +244,8 @@ Two limits, both deliberate:
228
244
  ## Packs
229
245
 
230
246
  A pack is any object responding to `#install(builder, **opts)`. Options are optional: Ruby drops an
231
- empty `**` splat, so a pack taking none needs no keyword parameter.
247
+ empty `**` splat, so a pack taking none needs no keyword parameter. Put the pack in a file your
248
+ app already loads, such as `lib/redis_pack.rb`, and `use` it from the initializer:
232
249
 
233
250
  ```ruby
234
251
  module RedisPack
@@ -250,7 +267,7 @@ exactly what `Briefly::Rails::Reload` does. The core stays framework-agnostic; p
250
267
  ### Options
251
268
 
252
269
  Keywords passed to `use` reach the pack's `install`. Ruby drops an empty `**` splat, so a pack that
253
- takes no options needs no keyword parameter:
270
+ takes no options needs no keyword parameter. The same pack file, now taking a `url:`:
254
271
 
255
272
  ```ruby
256
273
  module RedisPack
@@ -267,7 +284,8 @@ Api = Briefly.define { use RedisPack, url: "redis://cache:6379" }
267
284
  ### Short names
268
285
 
269
286
  `Briefly.register` maps a name to a pack, so `use` can take a string or symbol. There is no
270
- inflection and no path guessing; the registry is the only source of truth:
287
+ inflection and no path guessing; the registry is the only source of truth. Register in the
288
+ initializer, before the `define` that names it:
271
289
 
272
290
  ```ruby
273
291
  Briefly.register("myapp/redis", RedisPack) # a pack object
@@ -315,7 +333,7 @@ Nothing in the pack is memoized. `helpers`, `routes` and `renderer` are live loo
315
333
  already caches them on objects it refreshes on reload, so caching them again would only go stale.
316
334
  It still composes `Briefly::Rails::Reload`, because *your* memoized shortcuts need clearing.
317
335
 
318
- Need a custom renderer? Override it; last declaration wins:
336
+ Need a custom renderer? Override it in the initializer; last declaration wins:
319
337
 
320
338
  ```ruby
321
339
  App = Briefly.define do
@@ -324,7 +342,7 @@ App = Briefly.define do
324
342
  end
325
343
  ```
326
344
 
327
- `Briefly::Rails` is an umbrella over five packs, each usable on its own:
345
+ `Briefly::Rails` is an umbrella over six packs, each usable on its own:
328
346
 
329
347
  | pack | short name | shortcuts |
330
348
  |---|---|---|
@@ -333,6 +351,9 @@ end
333
351
  | `Briefly::Rails::View` | `"rails/view"` | `helpers`, `routes`, `renderer`, `render` |
334
352
  | `Briefly::Rails::DB` | `"rails/db"` | `connection`, `transaction`, `select`, `query`, `connected_to`, `reading`, `writing` |
335
353
  | `Briefly::Rails::Instrument` | `"rails/instrument"` | `instrument` |
354
+ | `Briefly::Rails::Reload` | `"rails/reload"` | none; clears memos on every code reload |
355
+
356
+ Take only the parts you want, in the initializer:
336
357
 
337
358
  ```ruby
338
359
  Worker = Briefly.define do
@@ -346,14 +367,16 @@ end
346
367
 
347
368
  | shortcut | aliases | value |
348
369
  |---|---|---|
349
- | `connection` | `conn` | forwards keywords and the block to `base.with_connection` yields the connection, auto-releases |
370
+ | `connection` | `conn` | forwards keywords and the block to `base.with_connection`, yielding the connection and auto-releasing |
350
371
  | `transaction` | `txn` | forwards keywords and the block to `base.transaction` |
351
- | `select` | | `base.with_connection { \|c\| c.select_all(sql) }` a read (SELECT) on the cache-aware path |
352
- | `query` | | `base.with_connection { \|c\| c.exec_query(sql) }` arbitrary SQL, writes and DDL included |
372
+ | `select` | | `base.with_connection { \|c\| c.select_all(sql) }`, a read (SELECT) on the cache-aware path |
373
+ | `query` | | `base.with_connection { \|c\| c.exec_query(sql) }`, arbitrary SQL, writes and DDL included |
353
374
  | `connected_to` | | forwards every argument to `base.connected_to` (`role:`, `shard:`, `prevent_writes:`) |
354
375
  | `reading` | | runs the block under the `:reading` role |
355
376
  | `writing` | | runs the block under the `:writing` role |
356
377
 
378
+ Declare the namespace in the initializer, then call it from application code:
379
+
357
380
  ```ruby
358
381
  App = Briefly.define do
359
382
  use "rails"
@@ -365,14 +388,14 @@ App.db.conn { |c| c.select_value("select count(*) from users") }
365
388
  ```
366
389
 
367
390
  `select` and `query` are the two raw-SQL helpers, differing only in which adapter path they take.
368
- `select(sql, *binds)` runs a read through `select_all` the path Rails recommends for a raw SELECT,
391
+ `select(sql, *binds)` runs a read through `select_all`, the path Rails recommends for a raw SELECT,
369
392
  returning an `ActiveRecord::Result` without clearing the query cache. `query(sql, *binds)` runs
370
393
  arbitrary SQL through `exec_query`: reads, writes, and DDL all execute. The name tells you which
371
394
  you're reaching for; neither polices the SQL it's handed, so `select` will happily run a write you
372
- give it the split is name and cache-path, not a runtime guard.
395
+ give it. The split is name and cache-path, not a runtime guard.
373
396
 
374
397
  Both sanitize through `base.sanitize_sql_array` when binds are given, and pass the statement through
375
- untouched when they are not. Positional and named binds both work:
398
+ untouched when they are not. Positional and named binds both work, from application code:
376
399
 
377
400
  ```ruby
378
401
  App.db.select("select * from users where name like '%ada%'") # no binds, passed through
@@ -388,12 +411,12 @@ not a convenience: always pass untrusted values as binds. The bindless path is u
388
411
  merely a read one.
389
412
 
390
413
  `connection`/`conn` mirrors `transaction`: it forwards to `with_connection`, yields the leased
391
- connection, and auto-releases at block exit, so nothing leaks outside a request. It requires a block
414
+ connection, and auto-releases at block exit, so nothing leaks outside a request. It requires a block;
392
415
  there is no bare-lease accessor. Anyone who genuinely needs a held raw lease calls `lease_connection`
393
416
  on their model directly.
394
417
 
395
- `reading` and `writing` *route* a block everything inside runs under that connection role, so you
396
- send specific reads to a replica or pin a write to the primary:
418
+ `reading` and `writing` *route* a block. Everything inside runs under that connection role, so from
419
+ application code you send specific reads to a replica or pin a write to the primary:
397
420
 
398
421
  ```ruby
399
422
  App.db.reading { App.db.select("select * from reports") } # runs on the replica
@@ -401,7 +424,8 @@ App.db.writing { Audit.create!(event: "export") } # pinned to the prima
401
424
  ```
402
425
 
403
426
  They're sugar over `connected_to`, which is also a shortcut in its own right and forwards the whole
404
- Rails surface any role (not just reading/writing), plus `shard:` and `prevent_writes:`:
427
+ Rails surface: any role, not just reading and writing, plus `shard:` and `prevent_writes:`. Also
428
+ from application code:
405
429
 
406
430
  ```ruby
407
431
  App.db.connected_to(role: :analytics) { Report.all.to_a } # a custom role
@@ -413,7 +437,7 @@ The `reading`/`writing` sugar pins its role: a `role:` you pass through it can't
413
437
  always reads. All of this needs a multi-database app: `base` must be `ActiveRecord::Base` or the
414
438
  abstract class that declared `connects_to(database: { ... })`. On a concrete model `connected_to`
415
439
  raises `NotImplementedError`, and in a single-database app with no matching connection
416
- `App.db.reading { ... }` raises `ActiveRecord::ConnectionNotDefined` these are a multi-DB tool, not a
440
+ `App.db.reading { ... }` raises `ActiveRecord::ConnectionNotDefined`. These are a multi-DB tool, not a
417
441
  role flag.
418
442
 
419
443
  **Pass `base:` as a String, not the class.** A pack is `use`d from an initializer, where naming an
@@ -425,8 +449,8 @@ The pack memoizes nothing and wires no lifecycle hook, so it works without a boo
425
449
 
426
450
  ### `Briefly::Rails::Reload`
427
451
 
428
- `Briefly::Rails` composes it. Use it alone for a facade with no framework shortcuts that still
429
- memoizes objects holding on to reloadable application classes:
452
+ `Briefly::Rails` composes it. Use it alone, in an initializer, for a facade with no framework
453
+ shortcuts that still memoizes objects holding on to reloadable application classes:
430
454
 
431
455
  ```ruby
432
456
  Admin = Briefly.define do
@@ -454,7 +478,7 @@ bodies call into each other can deadlock, like any pair of mutually-locking obje
454
478
 
455
479
  ## Testing
456
480
 
457
- Shortcuts are real methods, so nothing special is needed:
481
+ Shortcuts are real methods, so nothing special is needed in a test file:
458
482
 
459
483
  ```ruby
460
484
  allow(App).to receive(:redis).and_return(fake_redis) # rspec-mocks verifies it
@@ -481,6 +505,8 @@ We do not fake this with an RBS-only `method_missing`; the gem has none.
481
505
 
482
506
  ## Migrating a hand-rolled `App`
483
507
 
508
+ What the initializer replaces, and what it becomes:
509
+
484
510
  ```ruby
485
511
  # before
486
512
  module App
@@ -506,9 +532,33 @@ end
506
532
  memoized, and `redis` (which you *do* want memoized) is cleared on every dev reload by the Reload
507
533
  pack that `Briefly::Rails` composes.
508
534
 
535
+ ## Versioning
536
+
537
+ `briefly` follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The public API it
538
+ covers is what an application touches: `Briefly.define`, `Briefly.register`, `Briefly.pack`,
539
+ `Briefly.rescue_from` and `Briefly.rescues`; the `Builder` DSL inside a `define` block (`shortcut`,
540
+ `namespace`, `rescue_from`, `use`); the refinements `shortcut` returns (`memoize`, `rescue_from`);
541
+ the management surface behind `App.briefly`; the canonical names and aliases every shipped pack
542
+ installs; and the error classes under `Briefly::Error`.
543
+
544
+ Outside that promise: anything a shortcut compiles to internally, every `__`-prefixed private method
545
+ on `Facade`, and the RBS signatures in `sig/`, which describe the static surface and are revised
546
+ alongside it.
547
+
548
+ While the version stays below `1.0.0`, an incompatible change ships in a MINOR bump and a compatible
549
+ fix in a PATCH, so a MINOR bump is the one that may need you to read the changelog.
550
+
509
551
  ## Contributing
510
552
 
511
- Bug reports and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
553
+ Bug reports and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md), and
554
+ [CHANGELOG.md](CHANGELOG.md) for what has changed between releases.
555
+
556
+ Questions and bug reports both go to [GitHub Issues](https://github.com/svyatov/briefly/issues).
557
+
558
+ ## Maintenance
559
+
560
+ One person maintains `briefly`, actively, in their spare time. Issues and pull requests get a reply,
561
+ and a bug on a supported Ruby or Rails version gets a fix. A large feature request may not.
512
562
 
513
563
  ## License
514
564
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Briefly
4
4
  # The gem version.
5
- VERSION = "0.2.0"
5
+ VERSION = "0.2.1"
6
6
  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.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leonid Svyatov
@@ -84,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
84
  - !ruby/object:Gem::Version
85
85
  version: '0'
86
86
  requirements: []
87
- rubygems_version: 4.0.12
87
+ rubygems_version: 4.0.16
88
88
  specification_version: 4
89
89
  summary: A terse, curated facade over your application's most reached-for objects.
90
90
  test_files: []