8 repliesDiscussion2h ago
Index
Feed
Maintained by tobias.f · updated Aug 3, 2026
Twelve years of Rails opinions distilled for an agent: fat models within reason, no service-object cargo cult, bin/rails test as the heartbeat, and migrations treated like the loaded weapons they are.
- `app/models/` — domain logic lives here. Fat models are fine; a model over ~300 lines
gets a concern extracted (`app/models/concerns/`, named after the capability: `Archivable`).
- `app/controllers/` — thin. Auth, params, one domain call, respond. Logic in a controller is a smell.
- `app/jobs/` — Solid Queue. Jobs are idempotent; assume every job runs twice.
- `app/views/` — ERB + Hotwire (Turbo frames/streams + sparse Stimulus). No React. Do not add React.
- `test/` — Minitest + fixtures. Fixtures over factories; they're in `test/fixtures/*.yml`.- Test all: `bin/rails test` (fast, run it often)
- One file: `bin/rails test test/models/invoice_test.rb`
- System tests: `bin/rails test:system` — only when you changed views or Stimulus controllers
- Lint: `bin/rubocop -a` (safe autocorrect only; never `-A`)
- Console experiments: `bin/rails runner` one-liners. NEVER `bin/rails console` in a session — it hangs waiting for input.CLAUDE.md
# CLAUDE.md — kontor (Rails 8 monolith)
Boring Rails on purpose: Hotwire, Minitest, Postgres, Solid Queue. No microservices,
no GraphQL, no RSpec. When in doubt, do the most conventional Rails thing.
## Map
- `app/models/` — domain logic lives here. Fat models are fine; a model over ~300 lines
gets a concern extracted (`app/models/concerns/`, named after the capability: `Archivable`).
- `app/controllers/` — thin. Auth, params, one domain call, respond. Logic in a controller is a smell.
- `app/jobs/` — Solid Queue. Jobs are idempotent; assume every job runs twice.
- `app/views/` — ERB + Hotwire (Turbo frames/streams + sparse Stimulus). No React. Do not add React.
- `test/` — Minitest + fixtures. Fixtures over factories; they're in `test/fixtures/*.yml`.
## Commands
- Test all: `bin/rails test` (fast, run it often)
- One file: `bin/rails test test/models/invoice_test.rb`
- System tests: `bin/rails test:system` — only when you changed views or Stimulus controllers
- Lint: `bin/rubocop -a` (safe autocorrect only; never `-A`)
- Console experiments: `bin/rails runner` one-liners. NEVER `bin/rails console` in a session — it hangs waiting for input.
## Conventions
- Validations in the model, constraints in the database. BOTH, not either.
- Callbacks only for the model's own state (normalize, compute). Reaching into other
models from a callback is prohibited — that logic wants to be an explicit method.
- No new service-object hierarchy. A plain class in `app/models` with a verb method is
enough (`InvoiceReconciliation.run(batch)`). Skip the `app/services` cargo cult.
- Strong params stay in the controller, named `invoice_params` style. No permit! ever.
## Migrations
- Generate with `bin/rails g migration`, review the SQL mentally before running.
- Data migrations are separate from schema migrations, always reversible, and live in
`db/data_migrate/` — do not backfill inside a schema migration.
- NEVER edit a migration that has left your machine (i.e. is on main). Add a new one.
- After migrating: `bin/rails db:test:prepare` or the test DB will lie to you.
## Done means
1. `bin/rails test` fully green — not just the files you touched.
2. `bin/rubocop` clean on changed files.
3. Schema changes: `db/schema.rb` committed, migration reversible (`bin/rails db:rollback` works).
4. No new gems. Wishlist goes in the PR description.
Added the bin/rails console prohibition — Claude opened an interactive console and the session sat blocked for 40 minutes.
Jul 12, 2026Added 'db:test:prepare after migrating' after a session where every test failure was really a stale test schema.
Jun 17, 2026Running this config?
Sign in to add your report — every count here is backed by a named account.
- Validations in the model, constraints in the database. BOTH, not either.
- Callbacks only for the model's own state (normalize, compute). Reaching into other
models from a callback is prohibited — that logic wants to be an explicit method.
- No new service-object hierarchy. A plain class in `app/models` with a verb method is
enough (`InvoiceReconciliation.run(batch)`). Skip the `app/services` cargo cult.
- Strong params stay in the controller, named `invoice_params` style. No permit! ever.- Generate with `bin/rails g migration`, review the SQL mentally before running.
- Data migrations are separate from schema migrations, always reversible, and live in
`db/data_migrate/` — do not backfill inside a schema migration.
- NEVER edit a migration that has left your machine (i.e. is on main). Add a new one.
- After migrating: `bin/rails db:test:prepare` or the test DB will lie to you.1. `bin/rails test` fully green — not just the files you touched.
2. `bin/rubocop` clean on changed files.
3. Schema changes: `db/schema.rb` committed, migration reversible (`bin/rails db:rollback` works).
4. No new gems. Wishlist goes in the PR description.Strong evidence gets promoted into the record above.
Sign in to join the discussion, vote, and verify fixes.
Related records
'Assume every job runs twice' is six words doing the work of a whole reliability chapter. My agent started adding idempotency keys to jobs unprompted after I stole this file. More instructions like this — behavioral assumptions, not just rules.