Skip to content

Example CLAUDE.md — Ruby on Rails Project

This example shows a CLAUDE.md for a Rails 8 application using Hotwire for interactivity, Solid Queue for background jobs, and RSpec for testing. It covers the conventions that keep Claude Code generating idiomatic Rails code.

The CLAUDE.md File

# Project: Acme Marketplace

Rails 8 + Ruby 3.3 + PostgreSQL. Hotwire (Turbo + Stimulus) for frontend. Deployed on Kamal.

## Commands

- `bin/dev` — start dev server (Procfile.dev: Rails + CSS + JS)
- `bin/rails test` — run all Minitest tests
- `bundle exec rspec` — run all RSpec tests
- `bundle exec rspec spec/models/user_spec.rb` — single test file
- `bundle exec rspec spec/models/user_spec.rb:42` — single test by line
- `bin/rubocop` — lint check
- `bin/rubocop -a` — auto-fix lint issues
- `bin/rails db:migrate` — apply pending migrations
- `bin/rails db:rollback` — undo last migration

Always run `bin/rubocop` before committing.

## Architecture

- `app/models/` — ActiveRecord models with validations and scopes
- `app/controllers/` — thin controllers, business logic lives in models or services
- `app/services/` — service objects for complex operations (Checkout, Onboarding, etc.)
- `app/views/` — ERB templates organized by controller
- `app/components/` — ViewComponent classes for reusable UI
- `app/jobs/` — Solid Queue background jobs
- `app/mailers/` — ActionMailer classes
- `config/routes.rb` — all routes, RESTful resources only

## Rails Conventions

- Follow Rails conventions — if Rails has a way to do it, use that way
- Thin controllers: no more than 5 lines per action. Extract to service objects.
- Fat models are OK for queries and validations, but extract complex logic to concerns or services
- Use `has_many :things, dependent: :destroy` — always specify dependent behavior
- Scopes over class methods for reusable queries
- Use Strong Parameters in every controller action that accepts input

## Database

- Migrations live in `db/migrate/` — always use `bin/rails generate migration`
- Never edit `db/schema.rb` manually — it is auto-generated
- Add database-level constraints (NOT NULL, unique indexes) in addition to model validations
- Use `add_index` for any column used in WHERE clauses or joins
- Foreign keys: always add `add_foreign_key` in migrations

## Frontend (Hotwire)

- Use Turbo Frames for partial page updates — no full-page reloads for navigation
- Use Turbo Streams for real-time updates and form responses
- Stimulus controllers in `app/javascript/controllers/` — keep them small and focused
- No React, Vue, or other SPA frameworks — Hotwire handles all interactivity
- CSS: Tailwind CSS via cssbundling-rails

## Testing

- RSpec for model, request, and system tests
- FactoryBot for test data — factories in `spec/factories/`
- Use `let` and `subject` for setup, `before` blocks for shared state
- Request specs over controller specs — test the full HTTP cycle
- System tests with Capybara for critical user flows only (login, checkout, signup)
- Mock external services with WebMock — no real HTTP calls in tests

## Background Jobs

- Use Solid Queue for all async work (emails, webhooks, reports)
- Job classes in `app/jobs/` — keep them thin, delegate to service objects
- Always set `retry_on` with reasonable limits and backoff
- Log job arguments for debugging: `logger.info "Processing order #{order_id}"`

## Git

- Conventional commits: feat:, fix:, chore:, refactor:
- Scope by domain: `feat(orders): add bulk discount pricing`
- One migration per PR when possible

## Do NOT

- Do not use `skip_before_action` to bypass auth — create a separate unprotected controller
- Do not edit `db/schema.rb` manually
- Do not add JavaScript SPA frameworks — use Hotwire
- Do not use `update_column` or `update_all` unless you explicitly intend to skip validations and callbacks
- Do not use `find_by_sql` — use ActiveRecord query interface or Arel

Key Sections Explained

Rails Conventions — "Follow Rails conventions" seems obvious but Claude sometimes invents non-standard patterns. The thin controller rule and service object pattern keep code organized.

Database — "Never edit schema.rb" is critical. Claude may try to add columns directly to the schema file instead of creating a migration.

Frontend (Hotwire) — Explicitly blocking SPA frameworks prevents Claude from suggesting React or Vue when Hotwire is the team's choice. The Turbo Frames/Streams guidance helps Claude use the right tool for each interaction pattern.

Background Jobs — The retry_on rule prevents Claude from creating jobs that fail silently or retry infinitely.

See Also