// Config Record
>Ruby on Rails 8
Claude Code instructions for modern Rails 8 apps with Solid adapters, params.expect, and service-friendly conventions.
author:
dotmd Team
license:CC0
published:Feb 23, 2026
// Installation
>Add this file to your project repository:
- Claude Code--path=
CLAUDE.md
// File Content
CLAUDE.md
1# CLAUDE.md — Ruby on Rails 823## Project Overview45This is a Ruby on Rails 8 application. Rails 8 ships with Solid Queue, Solid Cache, Solid Cable, Propshaft, and import maps as defaults. Do not introduce Redis, Sidekiq, Sprockets, or Webpacker — they are not needed.67## Commands89```bash10# Development11bin/rails server # Start dev server (port 3000)12bin/rails console # Interactive console13bin/rails routes # Show all routes (pipe to grep)14bin/dev # Start all processes via Procfile.dev (Solid Queue, CSS watch, etc.)1516# Testing17bin/rails test # Run all tests18bin/rails test test/models/ # Run model tests only19bin/rails test test/system/ # Run system tests (requires headless Chrome)20bin/rails test test/models/user_test.rb:42 # Run single test by line number2122# Database23bin/rails db:migrate # Run pending migrations24bin/rails db:rollback # Undo last migration25bin/rails db:seed # Run seeds26bin/rails db:reset # Drop, create, migrate, seed2728# Generators29bin/rails generate model User name:string email:string:uniq30bin/rails generate controller Articles index show31bin/rails generate migration AddRoleToUsers role:integer32bin/rails generate authentication # Rails 8 built-in auth (generates User, Session, passwords)33bin/rails destroy model User # Undo a generator3435# Code quality36bin/rubocop # Lint (if present)37bin/brakeman # Security scan (if present)38```3940## Architecture — Rails 8 Defaults4142### Solid Adapters (not Redis)4344Rails 8 uses database-backed adapters by default:4546- **Solid Queue** — Background jobs. Config in `config/solid_queue.yml` and `config/queue.yml`. Workers run via `bin/jobs` or as part of `bin/dev`.47- **Solid Cache** — Cache store. Config in `config/solid_cache.yml`. Set in `config/environments/production.rb` as `config.cache_store = :solid_cache_store`.48- **Solid Cable** — Action Cable adapter. Config in `config/solid_cable.yml` and `config/cable.yml`.4950These use separate SQLite databases in development (`db/queue.sqlite3`, `db/cache.sqlite3`, `db/cable.sqlite3`). Do not add `redis` to the Gemfile for caching or jobs.5152### Propshaft (not Sprockets)5354Asset pipeline is Propshaft. There is no `app/assets/config/manifest.js`. Assets in `app/assets/` are served directly — no compilation, no fingerprinting config files. CSS goes in `app/assets/stylesheets/`. If the project uses Tailwind, it compiles via `bin/rails tailwindcss:build`.5556### Import Maps (not Webpack/esbuild)5758JavaScript uses import maps by default. Pin dependencies in `config/importmap.rb`:5960```ruby61pin "application"62pin "@hotwired/turbo-rails", to: "turbo.min.js"63pin "@hotwired/stimulus", to: "stimulus.min.js"64pin_all_from "app/javascript/controllers", under: "controllers"65```6667Add new JS deps with `bin/importmap pin <package>`, not `yarn add` or `npm install`. If the project uses jsbundling-rails instead, there will be a `package.json` — check before assuming import maps.6869### Kamal Deployment7071Rails 8 ships with Kamal for deployment. Config lives in `config/deploy.yml` and `.kamal/`. Secrets go in `.kamal/secrets` (sourced from env or credential files). Do not commit secrets or modify `.kamal/secrets` without asking.7273## File Structure Conventions7475```76app/77 models/ # Business logic, validations, associations, scopes78 controllers/ # Thin — params, auth, delegation to models79 views/ # ERB templates (or whatever the project uses)80 jobs/ # ApplicationJob subclasses (Solid Queue)81 mailers/ # ActionMailer classes82 helpers/ # View helpers (use sparingly)83 channels/ # Action Cable channels (Solid Cable)84 javascript/ # Import-mapped JS, Stimulus controllers85 controllers/ # Stimulus controllers (named *_controller.js)86config/87 routes.rb # Route definitions88 database.yml # DB config (SQLite default in dev)89 solid_queue.yml # Queue backend config90 importmap.rb # JS dependency pins91db/92 migrate/ # Timestamped migrations93 schema.rb # Authoritative schema (auto-generated, committed)94 seeds.rb # Seed data95test/96 models/ # Unit tests97 controllers/ # Functional tests98 system/ # Integration tests (Capybara + headless Chrome)99 fixtures/ # Test data (YAML)100 test_helper.rb # Test configuration101```102103## Model Patterns104105### Associations106107Declare associations at the top of the model, before validations:108109```ruby110class Article < ApplicationRecord111 belongs_to :author, class_name: "User"112 has_many :comments, dependent: :destroy113 has_one_attached :cover_image # Active Storage114end115```116117Always specify `dependent:` on `has_many`. Use `dependent: :destroy` for owned records, `dependent: :nullify` for shared ones.118119### Validations120121```ruby122validates :email, presence: true, uniqueness: { case_sensitive: false }123validates :title, presence: true, length: { maximum: 255 }124validates :status, inclusion: { in: %w[draft published archived] }125```126127Prefer database-level constraints (NOT NULL, unique indexes) alongside model validations. Never rely on model validations alone for data integrity.128129### Scopes130131```ruby132scope :published, -> { where(status: "published") }133scope :recent, -> { order(created_at: :desc) }134scope :by_author, ->(user) { where(author: user) }135```136137Keep scopes simple and composable. If a scope has complex logic, extract to a class method.138139### Callbacks — Use Sparingly140141Acceptable: `before_validation`, `after_create_commit` (for jobs/broadcasts).142Avoid: `after_save`, `after_commit` for side effects that should be explicit. Prefer service objects or explicit method calls for complex workflows.143144```ruby145# Good — broadcasting after commit146after_create_commit -> { broadcast_prepend_to("articles") }147148# Bad — hidden side effect149after_save :send_notification, :update_analytics, :sync_to_crm150```151152### Enums (Rails 8 syntax)153154```ruby155enum :status, { draft: 0, published: 1, archived: 2 }, default: :draft156```157158Always back enums with integer columns and explicit mappings. Never use array syntax.159160## Controller Patterns161162### Keep Controllers Thin163164```ruby165class ArticlesController < ApplicationController166 before_action :set_article, only: %i[show edit update destroy]167168 def index169 @articles = Article.published.recent.limit(20)170 end171172 def create173 @article = Current.user.articles.build(article_params)174175 if @article.save176 redirect_to @article, notice: "Article created."177 else178 render :new, status: :unprocessable_entity179 end180 end181182 private183184 def set_article185 @article = Article.find(params.expect(:id))186 end187188 def article_params189 params.expect(article: [:title, :body, :status])190 end191end192```193194### Key Conventions195196- Use `params.expect` (Rails 8) instead of `params.require().permit()` — it's stricter and raises on missing keys.197- Return `status: :unprocessable_entity` on failed form submissions (Turbo requires this).198- Use `Current.user` (via `Current` attributes) instead of a `current_user` helper when the authentication generator is used.199- Respond with `redirect_to` on success, `render :action, status:` on failure.200201### Authentication (Rails 8 Built-in)202203If `bin/rails generate authentication` was run, the app has:204- `User` model with `has_secure_password`205- `Session` model for session management206- `Authentication` concern included in `ApplicationController`207- `Current` class with `.user` and `.session`208209Do not install Devise or other auth gems unless the project explicitly uses them.210211## Testing212213Rails uses **Minitest** by default. Do not switch to RSpec unless the project already uses it.214215### Model Tests216217```ruby218class ArticleTest < ActiveSupport::TestCase219 test "validates presence of title" do220 article = Article.new(title: nil)221 assert_not article.valid?222 assert_includes article.errors[:title], "can't be blank"223 end224225 test "published scope returns only published articles" do226 assert_includes Article.published, articles(:published_one)227 assert_not_includes Article.published, articles(:draft_one)228 end229end230```231232### Controller Tests233234```ruby235class ArticlesControllerTest < ActionDispatch::IntegrationTest236 test "should get index" do237 get articles_url238 assert_response :success239 end240241 test "should create article" do242 assert_difference("Article.count") do243 post articles_url, params: { article: { title: "Test", body: "Content" } }244 end245 assert_redirected_to article_url(Article.last)246 end247end248```249250### System Tests251252```ruby253class ArticleFlowTest < ApplicationSystemTestCase254 test "creating an article" do255 visit new_article_url256 fill_in "Title", with: "My Article"257 fill_in "Body", with: "Article content"258 click_on "Create Article"259260 assert_text "Article created"261 assert_text "My Article"262 end263end264```265266### Fixtures over Factories267268Rails default is fixtures (`test/fixtures/*.yml`). Use them. They're loaded once, fast, and support associations via labels:269270```yaml271# test/fixtures/articles.yml272published_one:273 title: First Article274 status: published275 author: alice # References users(:alice)276277draft_one:278 title: Draft Article279 status: draft280 author: alice281```282283Do not add FactoryBot unless the project already uses it.284285## Background Jobs (Solid Queue)286287```ruby288class ArticlePublishJob < ApplicationJob289 queue_as :default290291 def perform(article)292 article.update!(status: "published", published_at: Time.current)293 ArticleMailer.published_notification(article).deliver_later294 end295end296```297298Enqueue with `ArticlePublishJob.perform_later(article)`. For scheduled jobs, use `set(wait:)`:299300```ruby301ArticlePublishJob.set(wait: 1.hour).perform_later(article)302```303304Recurring jobs are configured in `config/recurring.yml`:305306```yaml307production:308 article_cleanup:309 class: ArticleCleanupJob310 schedule: every day at 3am311```312313## Database314315- **SQLite** is the default for development and can be used in production for moderate traffic.316- Always add indexes for foreign keys and columns used in WHERE/ORDER clauses.317- Use `change` in migrations (not `up`/`down`) unless the migration is irreversible.318- Never edit `db/schema.rb` by hand — it's auto-generated from migrations.319- Use `bin/rails db:migrate` then commit both the migration and the updated `schema.rb`.320321```ruby322class AddPublishedAtToArticles < ActiveRecord::Migration[8.0]323 def change324 add_column :articles, :published_at, :datetime325 add_index :articles, :published_at326 end327end328```329330## Turbo and Hotwire331332Rails 8 uses Hotwire (Turbo + Stimulus) for interactivity. Key rules:333334- Form submissions that fail must return `status: :unprocessable_entity` or Turbo won't replace the form.335- Use `turbo_stream` responses for partial page updates, not full JSON APIs for in-app interactions.336- Stimulus controllers live in `app/javascript/controllers/` and are auto-registered.337- Turbo Frames (`<turbo-frame>`) scope navigation. Turbo Streams update specific DOM elements.338339## Anti-Patterns to Avoid3403411. **Do not add gems that duplicate Rails 8 features.** No Sidekiq (use Solid Queue), no Redis gem for caching (use Solid Cache), no Devise (use built-in authentication unless already present).3423432. **Do not create service objects for simple CRUD.** Rails controllers and models handle this. Only extract services for complex multi-step workflows.3443453. **Do not use `respond_to` with JSON in a Hotwire app.** Use Turbo Streams for dynamic updates. JSON APIs are for separate API-only endpoints.3463474. **Do not put query logic in controllers.** Use model scopes or class methods.3483495. **Do not skip the migration and edit schema.rb.** Always create a migration.3503516. **Do not use `update_attribute` (skips validations).** Use `update` or `update!`.3523537. **Do not wrap single-record finds in rescue blocks.** Let `ActiveRecord::RecordNotFound` bubble up — Rails renders 404 automatically.3543558. **Do not create initializers for Solid Queue/Cache/Cable.** They are configured via YAML files in `config/`, not Ruby initializers.3563579. **Do not use `rails` directly.** Always use `bin/rails` to ensure the correct bundled version.35835910. **Do not add `attr_accessible` or `attr_accessor` for form fields.** Strong parameters (`params.expect`) handle mass assignment protection.360361## Credentials362363Rails uses encrypted credentials. Never commit secrets in plain text.364365```bash366bin/rails credentials:edit # Edit default credentials367bin/rails credentials:edit --environment production # Edit production credentials368```369370Access with `Rails.application.credentials.dig(:aws, :access_key_id)`.371372## Style373374- Two-space indentation (Ruby community standard).375- Use `frozen_string_literal: true` magic comment at the top of every Ruby file.376- Prefer `%i[]` for symbol arrays, `%w[]` for string arrays.377- Use guard clauses over nested conditionals.378- Name boolean methods with `?` suffix. Name dangerous methods with `!` suffix.379