Beautiful Rails (Campfire)

25 tutorials · nityeshagarwal

What Campfire Is, and What 'Beautiful Rails' Even Means

What Campfire Is, and What 'Beautiful Rails' Even Means

The legend for the whole series. Before any pattern, you need a way to read an unfamiliar Rails codebase without drowning — so this tutorial derives a reading strategy (the domain lives in app/models, the include line is the table of contents, routes.rb is the sitemap), draws the Campfire domain map from the real files, installs the one yardstick used in every later tutorial (count the edge cases this line absorbs for free), and previews the nine principles as a dependency graph so you understand why the sequence is the sequence.

Reading a codebase domain-first The include line as a model's table of contents routes.rb as the sitemap of every state change The yardstick: count the edge cases a line absorbs for free The Campfire domain map Convention at the boundary (the throughline) The nine principles as a dependency graph The four tracks: Orientation, Foundations, Principles, Capstones
The Rails Model & Active Record

The Rails Model & Active Record

A first-principles tour of the Rails model layer — how a table becomes an object, what the callback lifecycle's _commit boundary actually guarantees, why a scope is a chainable Relation, what enum and STI generate for free — grounded line-by-line in Campfire's 44-line message.rb and the models around it. The Foundations building block that the model-layer principles (P1, P2, P7, P9) are built on.

Active Record maps one table row to one object instance; columns become attributes Associations (belongs_to/has_many) declare a foreign-key relationship and give you a navigable object graph The callback lifecycle: before/after _save vs _create vs _commit, and which fire inside vs after the DB transaction _commit means after-durable — the single token that designs out the ghost-row bug class scope is a named, chainable, composable class method that returns a Relation (a query), never an array enum maps a word to a stored value and generates a whole family: predicates, bang-setters, and scopes STI maps a type column to subclasses that share one table; subclasses override only their one difference A model need not have a table (PORO models like Sound and FirstRun) and state can be the existence of a row An association can carry its own grammar via an association-extension block Foundations teach the mechanics; the worldview WHY lives in the Principles track (P1, P2, P7, P9)
Controllers & Routing: The Layer That Should Almost Disappear

Controllers & Routing: The Layer That Should Almost Disappear

Foundations F2. The mechanics of how an HTTP request finds Ruby and stays thin — routes as a sitemap, resourceful CRUD, before_action guards, strong params, controller inheritance — grounded in Campfire's verb-as-noun controllers, authorization-by-association, the secure-by-default auth vocabulary, and the bot path that IS the human path. Reference-flavored: mechanics + the 37signals way + pointers to P3, P6, P7.

routes.rb as the sitemap: resources/resource, scope module:, scope defaults:, direct the action as a method; implicit rendering; before_action in declaration order strong params as the allow-list (params.require.permit) verb-as-noun controllers: every state change is CRUD on a hidden noun authorization-by-association: the load IS the security boundary find_by! (hard 404) vs find_by + redirect (friendly nav) authentication as a vocabulary of class macros, secure-by-default one predicate (can_administer?) guarding every write via one before_action controller inheritance + super: the bot path IS the human path partition: one query, two lists
Views, Partials & Helpers: One Renderer, One Address

Views, Partials & Helpers: One Renderer, One Address

A Foundations tutorial on Rails views — ERB, partials, dom_id, layouts, and helpers — derived from first principles and grounded in how Campfire turns a message into HTML exactly once and addresses it the same way on every path. Builds the view-side mechanics that P4 (Convention Is Leverage) and P5 (One Renderer) later turn into a worldview.

A partial is a render-once fragment, and `render collection:` replaces the hand-written `.each` loop `dom_id(record)` and `dom_id(record, :prefix)` compute a stable DOM id FROM the model, so view and operation derive the same id instead of hand-typing matching strings One shared `_message` partial paints every path (first load, live append, refresh, edit) — there is no second renderer to drift Edit-in-place works because three files agree on `dom_id(message, :edit)`, so the controller's `edit` action is literally empty Layout regions via `content_for`/`yield`, and a CSS state driven by a `local_assigns` boolean Helpers are plain methods that give the view a domain vocabulary so logic stays out of the ERB `cached: true` on a collection render turns N fragment lookups into one batched read_multi
Turbo: Frames & Streams

Turbo: Frames & Streams

A Foundations tutorial on Turbo (Hotwire) in Campfire, taught from first principles for a JS-naive Rails builder. Derives Turbo Drive, Frames, and Streams, then shows the keystone mechanic: a Turbo Stream over HTTP and a model broadcast over WebSocket target the identical dom_id and reuse the identical partial, so the HTTP reply and the live update are literally the same HTML. Covers the optimistic-id to_key handshake, broadcasting as a deliberate explicit method (not a callback), edit-in-place replace, and the wake-from-sleep refresh diff — all grounded in real Campfire file:line. Points to P5 (One Renderer) and P4 (Convention Is Leverage).

Turbo Drive turns full-page navigation into fetch-and-swap Turbo Frames update one region independently Turbo Streams carry HTML + an action + a target dom_id over two transports (HTTP response and WebSocket broadcast) the wire carries HTML, not data one partial, every path: the stream reuses the same _message partial and the same dom_id broadcasting is a deliberate explicit method, not a callback the optimistic-id handshake: to_key makes dom_id resolve to the client-chosen UUID the de-dupe is deleted, not written edit-in-place via broadcast_replace to a matching dom_id target wake-from-sleep catch-up is a turbo_stream diff (append new, replace edited), not a reload the Rails-side Stimulus seam: the server stamps intent (maintain_scroll) as data on the wire
Turbo 8: Morphing & Live Refresh

Turbo 8: Morphing & Live Refresh

The push half of Turbo, grounded in Fizzy's Kanban board: how broadcasts_refreshes + morph turn "render the page again and diff it" into live multiplayer with no real-time code — and how a handful of declarations let a live refresh coexist with open menus, lazy frames, optimistic drags, and unsaved drafts. Net-new territory Campfire structurally lacks; the mechanics companion to P5 and the drag-and-drop capstone.

broadcasts_refreshes (refresh-don't-replace) turbo_refreshes_with method: :morph morph as reconciliation vs destructive replace touch: true as declarative fan-out multiplayer as an emergent property of conventions agreeing on a stream name self-healing morphing frames (morphReload) survive-morph attribute veto (before-morph-attribute) autosave: the timer is the dirty flag localStorage drafts that survive morph (derive-don't-store at the client layer) DOM-as-source-of-truth (autoresize, self-deleting form)
Keyboard-First UIs: Config-Driven Navigation, Hotkeys, Accessibility by Construction

Keyboard-First UIs: Config-Driven Navigation, Hotkeys, Accessibility by Construction

A full keyboard-driven Kanban board — arrow-key navigation across nested lists, hotkeys that postpone/close/assign, and screen-reader semantics that can't drift — built from one generic Stimulus controller configured by data-* attributes, with aria-selected as the cursor and the routing table holding the meaning of every key. The Rails lesson: the DOM attribute is the state, and config beats a fork-per-list.

the DOM attribute is the state (aria-selected as the cursor) config over forks: one generic controller varied by data-* values url-as-contract: the routing table decides what each hotkey means verb-as-noun hotkeys (POST creates a NotNow/Closure/SelfAssignment resource) accessibility-by-construction (selection is ARIA, capability subtracted in markup) Stimulus outlet as a declared wire from board-level keydown to the focused list nested navigable lists relayed through the DOM tree cursor-rehoming: re-derive a sane cursor from the fresh DOM after a morph removes the focused row bounded Promise.race (real morph signal vs 200ms fallback) for liveness
Hotwire Form Widgets That Stay Plain-Rails on the Wire

Hotwire Form Widgets That Stay Plain-Rails on the Wire

Rich form controls — a multi-select combobox, a self-submitting form, a self-erasing confirmation — built so the wire stays boring. The through-line: the client invents no wire format; it materializes ordinary HTML inputs and lets Rails parse what Rails already knows how to parse. Grounded in Fizzy's combobox controllers, auto-submit lifecycle, and additive form_with decorator helpers.

The client invents no wire format — materialize real form inputs, let params parse them A server-rendered <template> authors the field name once, on the side that also writes the permit list Single-select vs multi-select IS Rails' scalar-vs-array param distinction (name vs name[]) requestSubmit() preserves native validation and Turbo; form.submit() bypasses both Declare intent as data on the wire: data-action as a publisher/subscriber pipeline in ERB Self-submitting, self-erasing forms: the Turbo lifecycle as one object fired by connect() Additive decorator helpers: merge Stimulus controllers onto form_with via compact.join, never clobber
Jobs & Background Work: The Thinnest Thread Boundary

Jobs & Background Work: The Thinnest Thread Boundary

A first-principles foundations tutorial on Active Job in Campfire: why background work exists, why the trigger altitude (after_create_commit, not after_save) is the load-bearing decision, and why every Campfire job is a two-line thunk that delegates to a model method. Grounded in real file:line evidence from the send-a-message fan-out, the webhook delivery, the ban content sweep, and the web-push thread pool that escapes the Rails executor.

Active Job as a uniform interface over a swappable queue backend perform_later serializes records as GlobalIDs (pass the record, not the id) the trigger altitude: enqueue from after_create_commit so a rolled-back row is never picked up the job class as the thinnest possible thread boundary (a two-line thunk delegating to a model method) the _later/plain-method pairing where the guard lives on the wrapper, never inside the job the in-band/out-of-band split at the commit boundary (one bulk update_all vs push_later) work that must escape the Rails executor lives in lib/ with all Active Record reads done before posting to threads
Caching: The Key You Never Maintain

Caching: The Key You Never Maintain

A Foundations tutorial on Rails caching at two altitudes — fragment/Russian-doll caching in the view and HTTP caching in the controller — taught from first principles and grounded in Campfire's real code. The spine: the cache key is always DERIVED from the data (updated_at, an ETag, a signed token), never a hand-maintained version integer. touch: declares the dependency graph once on the association; stale?/fresh_when let an expensive action body never run on a hit. Ends with patterns to steal and points to P2 (Derive, Don't Store) and P4 (Convention Is Leverage).

Fragment caching with cache record do — the key embeds updated_at (cache_version) Russian-doll caching: nested fragments where a child can bust the parent belongs_to :parent, touch: true declares the cache dependency graph once on the association render collection: cached: true batches N fragment lookups into one read_multi HTTP caching: stale?(etag:) / fresh_when wrap an action so a 304 skips the body expires_in ... public:, stale_while_revalidate: pushing freshness to browser/CDN Per-path Cache-Control headers (immutable assets vs short-lived else) The unifying mechanic: the key is DERIVED from the data, never a stored version integer — change the content, change the key/URL The same change-the-content-change-the-URL idea at the route layer via signed token + v: updated_at
Concerns as a Mechanism

Concerns as a Mechanism

A first-principles tour of ActiveSupport::Concern as Campfire's composition tool — the three regions (included do, the module body, class_methods) and the precise rule for what goes where, grounded in message/searchable.rb, user/bot.rb, user/avatar.rb, and block_banned_requests.rb. The mechanism behind why message.rb is 44 lines and its include line reads like a spec. Foundations-flavored: mechanics + the 37signals way + a pointer to P8 (the worldview).

ActiveSupport::Concern as a mixin with three regions: included do, the module body, class_methods included do is the wiring harness: macros that change the host class (callbacks, scopes, associations) the module body is plain instance behavior class_methods do keeps a named constructor next to the trait it builds, so mint/verify can't drift the include line IS the table of contents / the spec you read before any method body a concern can self-register a request gate just by being listed (block_banned_requests) concerns solve the two pain points of plain Ruby mixins: include-order of dependent macros and the awkwardness of adding class-level macros from inside a module give behavior a home: co-locate a trait's wiring, behavior, and constructor (points to P8)
The Model Is the Truth — and It Owns Its Consequences

The Model Is the Truth — and It Owns Its Consequences

The first principle of the Rails ethos: a record is the single source of truth about a fact, and the effects of that fact coming into being belong to the model — except when they belong to the call path instead. Derives "fat model, skinny controller" from scratch as a forced consequence of one question (whose fact is this?), shows truth without a table (PORO models, state-by-row-existence), and grounds it in Campfire's message.rb:11-12.

the model owns the consequence callback vs explicit method (whose fact is this?) _commit means after-durable truth without a table (PORO models) state-machine-by-row-existence the association owns its own grammar fat model, skinny controller as a forced consequence
Derive, Don't Store

Derive, Don't Store

The Rails ethos principle that every fact you can recompute from data you already have is a fact you must not store — because a stored copy is a second source of truth that will eventually disagree with the first. Derived from first principles (read-state, presence, capability links), shown compounding with P1/P3/P4, and grounded in Campfire's one-timestamp read-state, TTL presence window, signed_id capabilities, derived mentions, and HTTP 304 caching.

derive, don't store a stored copy is a second source of truth read-state as one nullable timestamp presence is a question about time flags lie signed_id capability links (the credential IS the URL) purpose-scoped tokens with no token table or sweeper mentions are derived attachables, not parsed strings HTTP 304 as derive-freshness-from-an-etag count the edge cases this line absorbs for free
Security Is the Shape of Your Data Access

Security Is the Shape of Your Data Access

A principle tutorial: authorization is strongest when it isn't a guard you remember to add but the very query you write. Load every record through the current user and the leaking version becomes one you literally cannot type. Derives security-as-shape from first principles, shows it compounding with the model-owns-truth, CRUD-on-a-noun, concerns, and polymorphism principles, then grounds it in Campfire's reachable_messages, authentication macros, can_administer?, the ambient ban gate, and a fail-closed SSRF guard.

authorization-by-association the IDOR you cannot type load every record through the current user find_by! (hard 404) vs find_by + redirect (human nav) reachable_messages as the single source of truth for visibility secure-by-default authentication concern opt-out-by-name with allow_unauthenticated_access / allow_bot_access OR'd auth strategies in priority order conditional CSRF on .bot_key? one can_administer? predicate guarding every write subclass overrides the guard (Direct rooms) the ambient self-registering before_action (block_banned_requests) capability by subtraction (deny_bots by default) fail-closed SSRF guard (invalid means dangerous) defense-in-depth with no per-call vigilance
Convention Is Leverage

Convention Is Leverage

A principle tutorial on the load-bearing idea behind Rails' "magic": when both sides of a boundary ask the framework the same question — dom_id for identity, to_key for a record's key, touch: for cache freshness — the two things can never drift, because there's only one place that computes them. We derive why hand-typed id strings and hand-maintained cache versions rot, watch the principle interlock with One Renderer (P5), the model owning truth (P1), and Derive Don't Store (P2), and then prove it against Campfire's single _message partial, its edit-in-place frame ids agreed across three files, and its zero-bookkeeping Russian-doll cache.

Convention as the elimination of hand-kept synchronization: when both sides of a seam call the same framework function, drift becomes unwriteable dom_id is the address: a stable DOM identity computed FROM the model, so the view, the turbo_stream, the broadcast, the refresh, and the edit all derive the same id instead of copying a string The single _message partial as one renderer reused across every path, guarded against its optimistic twin by a one-line warning Edit-in-place as a convention handshake: a frame id (dom_id(message, :edit)) agreed across three files lets `def edit` be empty to_key named as the convention bridge that makes dom_id speak a client-chosen UUID (full worldview owned by P5) Russian-doll caching as convention doing the dependency graph: touch: true declares the freshness edge once on the association; the cache key is updated_at, derived not stored The yardstick applied to convention: count the bookkeeping a single conventional call deletes
One Renderer: HTML Over the Wire

One Renderer: HTML Over the Wire

The P5 principle tutorial of the Beautiful Rails (Campfire) series. Derives, from first principles and the real Campfire codebase, why you render a message's HTML exactly once on the server and ship that same HTML over every transport — and how the to_key identity convention makes the optimistic client node and the server's broadcast converge, so "the HTTP reply" and "the live update" stop being two features that drift. Three beats: first principles (the naive two-renderer Rails version, derived away), the beauty in combination with P4/P1/P9, and the 37signals evidence at real file:line.

One renderer: one partial paints every transport HTML over the wire — the wire carries HTML, not data The two-renderer drift bug (page render vs broadcast render) to_key as the optimistic-id handshake bridging ActiveModel identity to dom_id The de-dupe is deleted, not written Edit-in-place: broadcast a replace to spectators, redirect the actor, no branch Server declares intent as data on the wire (maintain_scroll), client honors it
Model Every State Change as CRUD on a Noun

Model Every State Change as CRUD on a Noun

A principle tutorial (P6) from the Beautiful Rails (Campfire) series. Derives, from first principles, why every verb you're tempted to bolt onto a controller — ban, reset, mute, switch-room — is really the create/update/destroy of a hidden noun. Shows the naive fat-controller-with-custom-verbs Rails you'd write first, then how naming the noun collapses the controller to a tiny resourceful set and keeps routes.rb flat. Grounded in Campfire's bans/join_codes/bot-keys controllers, scope module:/scope defaults:, and open-a-room-is-just-GET-#show, with the principle composing against P1 (fat model), P3 (security as shape), and P4/P5 (one renderer).

verb-as-noun: every custom controller verb is CRUD on a hidden noun find the noun: the move that collapses a fat controller into a resourceful seven actions the two-line CRUD controller: HTTP-to-method translation, work lives on the model scope module: makes the controller folder tree mirror routes.rb without changing URLs scope defaults: { user_id: 'me' } makes a path helper argument-free open/switch a room is just GET #show — a read, not a verb one render path, two pagination scopes (last_page vs page_around) skinny controller is not discipline you impose — it's what's left when every action is genuine CRUD
Polymorphism Over Conditionals

Polymorphism Over Conditionals

A principle tutorial in the Beautiful Rails (Campfire) series. Teaches that every if-this-type/elsif-that-type branch is a polymorphism you haven't named yet — and how pushing the difference into an STI subclass, an ordered enum, or a super call makes the case statement disappear along with the bugs hiding in its branches. Grounded in Campfire's three room subclasses, the involvement enum, and the bot controller that inherits the human create path.

polymorphism you haven't named yet the branch was a missing abstraction STI: one table, a type column, subclasses that override only the seam enum as a query vocabulary (prefix:) capability by subtraction controller inheritance + super (the bot path IS the human path) type_previously_changed? as free dirty-tracking becomes! to recast a record's subclass muted as the absence of a .merge
Give Behavior a Home: Composition via Concerns

Give Behavior a Home: Composition via Concerns

A principle tutorial on why a Rails model's include line is its table of contents — how concerns file each trait where it belongs (host-changing wiring in included do, plain behavior in the module body, the named constructor beside the trait), so you know what a class can do before reading a single method. Grounded in Campfire's Message and User models, with Searchable and Bannable as the canonical concerns.

give behavior a home included do is the wiring harness the include line IS the spec / table of contents ActiveSupport::Concern's three regions: included do, module body, class_methods co-location: a trait's wiring, behavior, and constructor cannot desync concerns as where owned consequences (P1), self-registering gates (P3), and _later guards (P9) live full-text search as a self-contained ~25-line concern count the edge cases this line absorbs for free
Put Work at Its Right Altitude

Put Work at Its Right Altitude

The P9 principle tutorial for the Beautiful Rails (Campfire) series. Teaches the cross-cutting worldview that every unit of work has a correct altitude — in-band (cheap, must be durable for the response) or out-of-band (slow, flaky, fan-out) — and that the seam between them should be the thinnest possible thread boundary: a two-line job that only exists to be on another thread. Derives the principle from the fat after_save fan-out the reader would vibe-code, shows it compounding with P1, P8, P2/P3, and grounds every claim in real Campfire code (message.rb:12, room.rb:46-74, the three-line job thunks, web_push/pool.rb).

in-band vs out-of-band work the sync/async line the thinnest thread boundary the _commit boundary as the trigger altitude _commit means after-durable the ghost row jobs as thin thunks delegating to model methods the guard lives on the _later wrapper, not in the job GlobalID serialization: pass records, not ids-to-re-find work that must escape the Rails executor lives in lib/ do all Active Record reads before posting to threads altitude is decided at the seam, not by the model
The Line Where Saving Becomes a Product: One Message, Every Screen, Zero Glue

The Line Where Saving Becomes a Product: One Message, Every Screen, Zero Glue

A first-principles walk through Campfire's send-a-message flow, tracing a single message from the sender hitting Enter to its identical appearance on every connected screen. We follow the arc in order through five verified seams — the after_create_commit trigger, the sync/async fan-out, the one-partial broadcast, the to_key override that deletes an entire de-dupe bug class, and the decoupled back-half — showing at each stop that the MODEL, not the controller, owns the real-time experience. Every claim is grounded in quoted Campfire code, and each section contrasts the elegant version against the naive Rails code you'd vibe-code first, so the elegance lands by comparison.

after_create_commit and the meaning of _commit (after-durable) The model owning its own consequence vs the fat controller Mixin composition with ActiveSupport::Concern (the table-of-contents model) room.receive and the synchronous/asynchronous delivery split Composable AR scopes: visible.disconnected.where.not as a PM sentence Connectable time-window range scopes for presence enum prefix generating English-reading query scopes Turbo Streams over ActionCable with one server-rendered partial for all paths Overriding ActiveModel#to_key so dom_id speaks the client's chosen UUID Optimistic-UI de-duplication that falls out of shared DOM identity Stimulus listening on Turbo's before-stream-render lifecycle event
Search: The Feature That Is Almost Entirely Convention

Search: The Feature That Is Almost Entirely Convention

A capstone that traces Campfire's full-text search end-to-end — indexed, ranked, authorized, paginated — and shows that almost none of the code is "about search." Four principles (concerns, derive-don't-store, security-as-shape, altitude) collaborate so a production search feature falls out of a 25-line concern, one composed scope, and a partial you already wrote.

Full-text search as a self-syncing FTS5 index, not a query trick Why Message.where("body LIKE ?") never even matches (the body lives in action_text_rich_texts) Searchable as a self-contained concern with three symmetric _commit callbacks plain_text_body deriving searchable text, with a filename fallback for image-only messages The search scope as a chainable Relation so auth and pagination compose onto it Current.user.reachable_messages.search(query) — the authorization boundary composed onto the search _commit keeping rolled-back rows out of the index The same _message partial rendering search results as every other surface Recent-searches as CRUD on a Search noun, self-trimming on create
The Ban Arc: Thin Controller, Orderly Model, Ambient Guard

The Ban Arc: Thin Controller, Orderly Model, Ambient Guard

A capstone tracing Campfire's ban feature end-to-end. Banning a user looks like a one-liner, but its correctness lives entirely in the ORDER of operations inside one model transaction — and four principles (CRUD-on-a-noun, the fat-model transaction, security-as-shape, and altitude) collaborate to make the block outlive the logout and the account itself. Grounded in user/bannable.rb, bans_controller.rb, ban.rb, and block_banned_requests.rb.

verb-as-noun: banning is creating a Ban the fat-model transaction as an ordered checklist transaction ordering as the load-bearing correctness detail snapshot durable state before you delete its source security as an ambient self-registering before_action the block outlives the session and the account altitude: defer the slow fan-out (content removal) to a job authorization-by-association at the request gate
Drag-and-Drop the Rails Way: Derived Order, REST Drops, Morph Reconciliation

Drag-and-Drop the Rails Way: Derived Order, REST Drops, Morph Reconciliation

A capstone synthesis: Fizzy's whole drag-and-drop interaction is built with almost no bespoke code because every layer defers to a convention the next layer understands — a derived sort order with no position column, each drop modeled as an ordinary REST resource so the routing table (not a controller branch) owns the meaning, a rich transactional model verb shared by two entry points, a server-rendered URL with an __id__ placeholder that carries the entire contract, and an optimistic move reconciled by morph and kept honest by a single SQL sort axis. The Rails-side ideas are load-bearing; the drag's JavaScript is background mechanism. Synthesizes P1, P2, P6, P5, and F4b.

Derived order with no position column (last_active_at as the one sort axis) State-change-as-REST-resource: the routing table owns the case-on-destination Thin drop controllers (3-7 lines) calling one rich model verb triage_into / close / postpone as transactional model verbs with two entry points URL-as-contract: __id__ placeholder swap; the route decides what a drop means Optimistic DOM move reconciled by morph (the to_key handshake, new mechanism) Top-or-bottom data attribute constrains the client's guess to the server's sort axis Config-as-data in data-* attributes; JS stays domain-agnostic verb-as-noun / CRUD-on-a-noun (STYLE.md as written house law) count the edge cases this line absorbs for free
Passwordless Login With No Tokens Table

Passwordless Login With No Tokens Table

A capstone: Fizzy ships a complete, enumeration-resistant, replay-safe magic-link login with a credentials table that has no `used` boolean, no `consumed_at`, and no pending-logins table at all. Three principles you've already met collaborate — derive-don't-store (consume means destroy, expiry is a clock comparison), security-as-the-shape-of-data-access (an unknown email constructs the same object as a known one, so the two responses are byte-identical), and CRUD-on-a-noun (the whole flow is the REST lifecycle of a magic link and a session). Grounded entirely in real Fizzy code; Campfire has no passwordless auth, so Fizzy is the sole worked example.

Passwordless magic-link authentication modeled as CRUD on a noun Consume-means-destroy: deriving 'spent' from a row's absence instead of a `used` flag Expiry as a read-time range comparison (beginless/endless ranges), not a status to sweep Pending-login state as a signed, httponly, self-expiring cookie instead of a server-side table Binding credential to browser via a constant-time email compare at redeem Anti-enumeration by structural identity: fabricating an unsaved MagicLink so unknown emails are byte-identical to known ones Thin controllers / rich model: every hard part lives in MagicLink and Session