All Ruby on Rails templates

Rails ActiveRecord Patterns

Model design, scopes, callbacks, concerns, N+1 prevention.

DevZone Tools1,620 copiesUpdated Feb 27, 2026Ruby on Rails
# CLAUDE.md — Rails ActiveRecord Patterns

## Migrations

- One migration per logical change. Never edit a migration after it's merged — write a new one.
- Reversible migrations by default. `change` is fine for simple operations; use `up`/`down` for complex ones.
- Add indexes when adding foreign keys: `t.references :user, foreign_key: true, index: true`.
- For large data backfills, use a separate `data:migrate` rake task — not the schema migration.

## Models

- Skinny associations, smart scopes:
  ```ruby
  class Invoice < ApplicationRecord
    belongs_to :customer
    has_many :line_items, dependent: :destroy

    scope :unpaid, -> { where(paid_at: nil) }
    scope :recent, ->(n = 30) { where("created_at > ?", n.days.ago) }
  end
  ```
- Always set `dependent:` on `has_many`. The default is silent orphans.
- Validations belong on the model. Don't validate in controllers or service objects.

## Callbacks

- Use sparingly. `before_validation` for normalization (downcase email). `after_create_commit` for events that should fire only after the DB is durable.
- Don't put cross-model side effects in callbacks. They run in spooky places (specs, console, batch imports). Use service objects.
- Skip callbacks intentionally with `update_columns` for one-off backfills — and document why.

## Queries

- `find` for primary key (raises if missing). `find_by` for other unique columns (returns nil).
- Avoid `find_by_<col>!` — `find_by(col: ...)!` is the modern equivalent.
- Eager-load with `includes(:assoc)`. Profile with `bullet` to catch N+1s.
- For complex reads, write a query object PORO. Don't bury 30-line scopes in the model.

## Concerns

- `app/models/concerns/` for reusable behavior. Name with the capability: `Sluggable`, `SoftDeletable`, `Auditable`.
- Concerns hold validations, scopes, and methods that always go together. They don't replace good model design.

## Counter caches & denormalization

- Counter caches: `belongs_to :post, counter_cache: true`. Adds `posts_count` increments — saves COUNT queries.
- Don't denormalize until you measure the cost. Caches drift.

## Soft delete

- If you must, add a `discarded_at:datetime` and use the `discard` gem.
- Default scope to non-discarded with care — it's surprising in batch operations.
- Most data shouldn't be soft-deleted. The "we might restore it later" is rarely real.

## Don't

- Don't use `update_attribute` (singular). It skips validations.
- Don't use `unscoped` to "see all rows" in production code. If a default scope hides too much, the scope is wrong.
- Don't write SQL via interpolation: `where("name = '#{params[:name]}'")` is injection. Use placeholders.
- Don't read all rows then filter in Ruby. Push the filter to SQL.

Other Ruby on Rails templates