Claude Code for Rails: Rules, Skills, and Guardrails
Set up Claude Code for Rails: rules for thin controllers, strong params, RSpec and RuboCop, plus a reusable skill for generators and migrations.
Rails is built on convention over configuration, which sounds like an ideal match for AI coding assistants. In practice, Claude Code for Rails produces tutorial-grade code until you pin your conventions down explicitly: business logic drifts into controllers, migrations get hand-written version timestamps, and specs stub everything into meaninglessness. The model has seen fifteen years of Rails in its training data — Rails 4 monoliths, omakase purism, service-object-heavy enterprise apps — and without rules it samples from all of them, often in the same session.
The fix is a three-layer setup: a Rails CLAUDE.md that pins your stack and architecture, path-scoped rules under .claude/rules/ for models, controllers, and specs, and a skill that wraps procedural workflows like generators and migrations. This guide walks through all three with blocks you can copy today. Everything here also works as general-purpose Ruby AI coding rules — the same content ports to Cursor, Windsurf, and any other agent your team runs.
Why Claude Code for Rails needs explicit rules
Convention over configuration is exactly why agents struggle with Rails. In an Express or FastAPI codebase, the architecture is visible in the code: routers, dependency wiring, explicit middleware. In Rails, huge amounts of behavior are implicit — naming conventions, autoloading, callbacks, schema.rb as the source of truth. Your team's actual conventions live in code review comments and tribal knowledge, which an agent cannot read.
That leaves the model to guess on every genuinely contested question in the Rails ecosystem:
- Fat models vs. service objects. Both are "idiomatic Rails" depending on who you ask.
- RSpec vs. Minitest, factories vs. fixtures. The single biggest fork in Rails testing.
- Callbacks vs. explicit orchestration.
after_createemail sending is all over the training data. - Hand-written migrations vs. generators. Agents love writing migration files directly, including inventing the timestamp.
- Hotwire vs. React sprinkles vs. jQuery-era patterns. Fifteen years of frontend churn, all equally represented.
Every one of these is a coin flip without rules. With rules, they are settled once and enforced on every generation. The general principles are covered in our guide to AI coding rules best practices; the rest of this post is the Rails-specific application.
Step 1: Pin core Rails rules — controllers, services, strong params
Start your Rails CLAUDE.md with the stack, then the architectural decisions agents most often get wrong. Keep it terse: these files are instructions, not documentation. (If you are starting from zero, the CLAUDE.md guide covers structure and loading behavior.)
## Stack
- Ruby 3.4, Rails 8.0
- Postgres, Sidekiq, Hotwire (Turbo + Stimulus)
- RSpec + FactoryBot for tests
## Architecture
- Controllers stay thin: authenticate, authorize, call one service
or model method, render/redirect. No business logic, no
multi-model orchestration in controllers.
- Business logic lives in app/services/. One class per use case,
named as a verb phrase (Orders::PlaceOrder) with a single
`call` method returning a result object.
- Always use strong params via a private `*_params` method.
Never pass raw `params` or `params.to_unsafe_h` into models.
- Model callbacks only for data integrity (normalization,
defaults). Never for side effects — emails, jobs, and API calls
belong in services.
- Prevent N+1 queries: use `includes`/`preload` whenever
iterating associations.
- Prefer plain Ruby objects over concerns for shared behavior.
These six rules target the highest-frequency failures. The controller rule matters most: without it, agents produce forty-line create actions that find records, branch on business conditions, send emails, and enqueue jobs inline. With it, you get controllers like this:
class OrdersController < ApplicationController
def create
result = Orders::PlaceOrder.call(user: current_user, params: order_params)
if result.success?
redirect_to result.order, notice: "Order placed."
else
@order = result.order
render :new, status: :unprocessable_entity
end
end
private
def order_params
params.require(:order).permit(:product_id, :quantity)
end
end
Note the negative rules ("never pass raw params"). Agents respond better to explicit prohibitions than to positive examples alone, because the prohibited pattern is usually the one most common in training data.
Step 2: Testing rules for RSpec and Minitest
Declare your test framework in the first line of your testing rules. This is the single highest-leverage testing rule in any Rails project, because an agent that guesses wrong produces files your suite will not even run.
If you are an RSpec shop:
## Testing (RSpec)
- RSpec with FactoryBot. Never generate fixtures.
- Request specs, not controller specs (controller specs are
soft-deprecated; do not create spec/controllers/).
- System specs with Capybara for critical user flows only.
- Use `let` for setup shared across examples; inline anything
used once.
- Never use `allow_any_instance_of`.
- Do not stub the object under test. Stub external services at
the HTTP boundary (WebMock), not internal collaborators.
- Group related assertions with `aggregate_failures` instead of
splitting one behavior across five examples.
If you are a Minitest shop, the rules are different and mostly about not importing the RSpec ecosystem:
## Testing (Minitest)
- Minitest with Rails fixtures. This is a fixtures codebase —
do NOT add FactoryBot or factories.
- Use `test "description"` blocks, not `def test_*` methods.
- Integration tests over controller tests for request flows.
- Run the suite with `bin/rails test`; it is parallelized,
so tests must not share mutable global state.
The "do NOT add FactoryBot" line looks redundant until you watch an agent helpfully add the gem, create test/factories/, and mix two data strategies in one suite. Agents default to the most popular pattern; if yours is the less popular one, say so explicitly.
Step 3: Align your rules with RuboCop so they reinforce each other
Rails teams already have a style enforcer, and your AI rules should lean on it rather than compete with it. The division of labor: RuboCop owns mechanical style (quotes, line length, method size, frozen string literals), rules own architecture and intent (where logic lives, what a service returns, when to ask before acting). Do not restate cops in prose — duplicated rules drift, and the agent can read .rubocop.yml directly.
What belongs in your rules file is the workflow contract:
## Linting
- `.rubocop.yml` is the source of truth for style. Read it
before writing Ruby; do not argue with it.
- After editing Ruby files, run
`bundle exec rubocop -a <changed files>` and fix any
remaining offenses before finishing.
- Never add `rubocop:disable` comments without asking first.
This pairing also gives you a useful decision rule for the team: if a convention can be expressed as a cop, make it a cop. Deterministic enforcement beats probabilistic instruction every time — the rule tells the agent your intent up front, and RuboCop catches the cases where generation drifted anyway. Reserve prose rules for things no cop can check, like "side effects belong in services" or "every null: false column gets a presence validation."
We use the same layered approach in our Django rules guide, where ruff plays RuboCop's role. The pattern is framework-agnostic: linter for the letter, rules for the spirit.
Step 4: Scope rules by path with .claude/rules and AGENTS.md
A root CLAUDE.md loads into every session, so keep it lean — stack, architecture, workflow. Everything else should load only where it applies. Claude Code supports path-scoped rule files under .claude/rules/, each with glob frontmatter:
---
description: ActiveRecord model conventions
globs: ["app/models/**/*.rb"]
---
- No queries inside callbacks; extract to scopes or services.
- Every `null: false` column gets a matching presence
validation; every `uniqueness` validation gets a unique index.
- New scopes that filter on unindexed columns require a
migration adding the index.
A typical Rails layout ends up with three or four of these: models.md, controllers.md, specs.md, maybe jobs.md. Each stays under a screen of text, and the agent only pays the context cost when it is actually touching those files.
For teams running more than one agent, add an AGENTS.md at the repo root. Codex CLI, Cursor, and a growing list of tools read it as a cross-tool standard, and nested AGENTS.md files in subdirectories (an engines/ folder, Packwerk packs) give the same path-scoping benefit for tools that do not support glob frontmatter. Keep one source of truth — either generate CLAUDE.md and AGENTS.md from the same content, or make one a pointer to the other. Divergent copies are worse than no rules, because half your Rails AI agents will follow the stale version.
Step 5: Build a Rails skill for generators and migrations
Rules describe what code should look like. Skills capture procedures — multi-step workflows with ordering and verification. Migrations are the canonical Rails example: agents that hand-write migration files invent timestamps, skip schema.rb verification, and edit already-merged migrations. A skill turns that into a checklist the agent loads on demand:
---
name: rails-migrations
description: Create and run Rails migrations safely. Use when
adding, changing, or removing columns, tables, or indexes.
---
# Rails migrations
1. Generate, never hand-write:
`bin/rails g migration AddStatusToOrders status:string`
2. Edit the generated file: add `null: false` and defaults
where the model requires them.
3. Add an index for any column used in `where`, `order`,
or a join.
4. Run `bin/rails db:migrate`, then check
`git diff db/schema.rb` — the diff must contain only
this change.
5. Never edit a migration that has been merged. Write a new one.
6. For destructive changes (dropping columns or tables), stop
and ask before running anything.
Because a skill is a folder package, not just one file, you can ship supporting material alongside the SKILL.md: a zero-downtime migration checklist, a template for backfill rake tasks, a script that greps new where clauses for missing indexes. See the SKILL.md format reference for frontmatter details.
The payoff comes when you share it. Publish the package once and every teammate installs it into every tool from one command:
localskills install your-org/rails-conventions --target claude cursor windsurf
The CLI writes each tool's native format from the same published version — .claude/skills/ for Claude Code, .mdc files under .cursor/rules (your Rails Cursor rules), .windsurf/rules for Windsurf. Versions are tracked with rollback, so updating a convention means publishing once instead of opening PRs against every repo's rules files. The docs cover publishing, folders, and team access controls.
Claude Code for Rails: FAQ
Does Claude Code work well with Rails and Ruby?
Yes — with the caveat that output quality tracks the specificity of your rules more than in most stacks. Ruby's expressiveness means there are more valid-looking ways to write any given change, and Rails' implicit conventions give the agent fewer structural signals to infer from. A tight CLAUDE.md plus path-scoped rules closes most of the gap.
Should a Rails project use CLAUDE.md or AGENTS.md?
Use CLAUDE.md if Claude Code is your only agent; add AGENTS.md the moment a second tool enters the picture. The content is nearly identical either way — the important thing is a single source of truth. Maintaining two divergent files by hand is how teams end up with agents enforcing different conventions in the same codebase.
How do I stop AI agents from putting business logic in controllers?
Three reinforcing layers: an explicit architecture rule ("controllers authenticate, authorize, call one service, render"), a path-scoped rule on app/controllers/** that repeats the prohibition where it is needed, and a named home for the logic (app/services/ with a naming convention). Agents follow "put it here" far more reliably than "don't put it there" alone.
Do AI rules replace RuboCop?
No, and they should not try. RuboCop is deterministic; rules are probabilistic. Encode everything mechanically checkable as a cop, tell the agent to run RuboCop on changed files, and spend your rules budget on architectural intent that no linter can verify.
Can I share Rails rules across Cursor, Claude Code, and Windsurf?
Yes. Publish your rules and skills as a package on localskills.sh and install with multiple targets — the CLI generates each tool's native format (.mdc rules for Cursor, skills for Claude Code, Windsurf rules) from one versioned source.
Stop copy-pasting rules files between repos and teammates. Create a free account, publish your Rails conventions once, and install them into every agent your team uses.
npm install -g @localskills/cli
localskills login
localskills install your-org/rails-conventions --target claude cursor windsurf