Modern Rails Rules
Rails 7+ best practices: conventions, generators, and the Omakase stack.
# CLAUDE.md — Modern Rails
## Stack
- Rails 7+ (Rails 8 if you can adopt it). Ruby 3.3+.
- The Omakase stack: **Hotwire** (Turbo + Stimulus), **Tailwind**, **importmap-rails** or **esbuild** for JS, **Solid Queue / Solid Cable / Solid Cache** for jobs/cache (Rails 8+).
- Skip the SPA. Server-rendered + Turbo handles 90% of "I need it to feel snappy" without React.
## Conventions
- "Convention over configuration" is the rule. When in doubt, do what `rails generate` does.
- One model per concept. Don't pile unrelated columns onto `User` because every other model joins to it.
- Skinny controllers, fat models — but don't let "fat models" become 1000-line god classes. Extract to **POROs** or service objects.
## Generators
- Use `bin/rails g model`, `g controller`, `g migration`. The defaults are good.
- Don't bypass generators to "save a step" — they keep test files, fixtures, and routes in sync.
- Customize generators in `config/generators` once if your defaults differ.
## Strong params
- Always permit explicitly:
```ruby
params.require(:user).permit(:email, :name, :role)
```
- Never `permit!`. That's an open door.
- Define `*_params` private methods on every controller. One per resource action.
## Routes
- RESTful first. Add custom actions only when REST genuinely doesn't fit:
```ruby
resources :invoices do
member { post :send_email }
end
```
- Nest at most one level deep. Beyond that, pull a separate controller.
## Concerns
- Use `app/models/concerns/` and `app/controllers/concerns/` for cross-cutting behavior with a clear name (`Sluggable`, `SoftDeletable`).
- Don't use concerns to "split a long file". Concerns are for reuse, not file organization.
## Service objects
- For multi-step operations, write a PORO with a single public method:
```ruby
class Invoices::Send
def initialize(invoice); @invoice = invoice; end
def call; ...; end
end
```
- Return a `Result.success(value)` / `Result.failure(error)` object. Don't raise from a service for known failure modes.
## Performance
- `bullet` gem in dev. Catches N+1s before they hit prod.
- `includes(:assoc)` for eager loading. `.preload` and `.eager_load` if you need to control the join strategy.
- `find_each` for iterating large tables. Default `each` loads everything.
## Don't
- Don't use callbacks for cross-resource side effects (`after_save` that touches a different table). Use service objects or jobs.
- Don't put business logic in views. Helpers exist; presenters / decorators exist.
- Don't store sensitive data in `Rails.cache` without `expires_in`. Cache stampedes are real.
- Don't disable Rails defaults (CSRF, ForceSSL, Strong Params) without understanding what you give up.
Other Ruby on Rails templates
Rails + Hotwire (Stimulus + Turbo)
Server-rendered frontends with Turbo Frames, Streams, and Stimulus.
Rails ActiveRecord Patterns
Model design, scopes, callbacks, concerns, N+1 prevention.
Rails Testing with RSpec
RSpec setup, factories, system specs, request specs, and fast suites.
Rails Deployment (Kamal + Docker)
Deploy Rails with Kamal, Docker, and the Solid Queue/Cable stack.