Back to skills
SKILL.md
Rails Idioms
ASecurityRuby on Rails framework patterns: ActiveRecord scopes, N+1 query prevention, model validations, RESTful routing, and RSpec/Minitest testing. Use when building, maintaining, or auditing Rails web applications and APIs. Pair with ruby-idioms.
- 157 stars
- 0 votes
- 0 copies
- 0 views
- Added September 5, 2026
Works with
Security analysis
100/100npx -y skills add irahardianto/antigravity-setup --skill rails-idioms --agent claude-codeAre you the author of Rails Idioms?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/irahardianto-rails-idioms)---
name: rails-idioms
description: >-
Ruby on Rails framework patterns: ActiveRecord scopes, N+1 query prevention, model validations, RESTful routing, and RSpec/Minitest testing. Use when building, maintaining, or auditing Rails web applications and APIs. Pair with ruby-idioms.
---
## Rails Idioms and Patterns
Rails rewards convention over configuration, Active Record, and RESTful design. Idiomatic Rails = conventional, tested, Hotwire-aware.
> Scope: Rails-specific patterns. For Ruby: `@.agents/skills/ruby-idioms/SKILL.md`.
### Active Record
1. **Scopes for reusable queries:**
```ruby
class Task < ApplicationRecord
scope :active, -> { where(status: :active) }
scope :by_priority, -> { order(priority: :desc) }
scope :created_after, ->(date) { where('created_at > ?', date) }
# ✅ Chainable
# Task.active.by_priority.created_after(1.week.ago)
end
```
2. **Validations in models, not controllers:**
```ruby
class Task < ApplicationRecord
validates :title, presence: true, length: { maximum: 200 }
validates :priority, inclusion: { in: %w[low medium high] }
# ✅ Custom validation
validate :deadline_must_be_in_future, on: :create
private
def deadline_must_be_in_future
return unless deadline.present? && deadline < Time.current
errors.add(:deadline, 'must be in the future')
end
end
```
3. **`includes`/`preload`** for eager loading — avoid N+1:
```ruby
# ❌ N+1 — fires a query per task to load user
Task.all.each { |t| puts t.user.name }
# ✅ Eager load — 2 queries total
Task.includes(:user).each { |t| puts t.user.name }
# ✅ Use Bullet gem to detect N+1 in development
```
4. **Callbacks — use sparingly, prefer service objects:**
```ruby
# ❌ Complex callback chains — hard to test and debug
before_save :normalize_title, :set_defaults, :notify_assignee
# ✅ Service object — explicit, testable
class CreateTask
def call(params)
task = Task.new(normalize(params))
task.save!
NotificationService.notify(task.assignee, task)
task
end
end
```
### Controllers
1. **RESTful actions** — only standard 7 actions per controller. Custom actions = new controller:
```ruby
# ❌ Custom action crammed into TasksController
def complete; end
# ✅ Dedicated controller
class TaskCompletionsController < ApplicationController
def create
task = Task.find(params[:task_id])
task.complete!
redirect_to task
end
end
```
2. **Strong parameters** — never mass-assign without permit:
```ruby
private
def task_params
params.require(:task).permit(:title, :priority, :deadline, :description)
end
```
3. **Service objects** for complex business logic:
```ruby
class TasksController < ApplicationController
def create
result = CreateTask.new.call(task_params)
if result.success?
redirect_to result.task, notice: 'Task created'
else
@task = result.task
render :new, status: :unprocessable_entity
end
end
end
```
### Error Handling
> For universal error handling principles, see `.agents/rules/error-handling-principles.md`.
1. **`rescue_from` for controller-level error handling:**
```ruby
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActiveRecord::RecordInvalid, with: :unprocessable
private
def not_found(exception)
render json: { error: exception.message }, status: :not_found
end
def unprocessable(exception)
render json: { errors: exception.record.errors.full_messages },
status: :unprocessable_entity
end
end
```
2. **Custom domain errors:**
```ruby
module TaskErrors
class NotAssignable < StandardError; end
class DeadlinePassed < StandardError; end
end
```
3. **Never rescue `Exception`** — always rescue `StandardError` or specific subclasses.
### Hotwire (7+)
1. **Turbo Frames** for partial page updates:
```erb
<!-- Wraps content that can be independently loaded/replaced -->
<%= turbo_frame_tag "task_#{task.id}" do %>
<%= render task %>
<% end %>
```
2. **Turbo Streams** for real-time updates:
```ruby
# In controller — auto-broadcasts updates
def create
@task = Task.create!(task_params)
respond_to do |format|
format.turbo_stream
format.html { redirect_to tasks_path }
end
end
```
3. **Stimulus** for JavaScript sprinkles — minimal JS.
### Security
> For universal security principles, see `.agents/rules/security-principles.md`.
- **CSRF protection** — enabled by default, never disable
- **Content Security Policy** — configure in `config/initializers/content_security_policy.rb`
- **Parameterized queries** — Active Record handles this, but never use string interpolation in `where`:
```ruby
# ❌ SQL injection risk
Task.where("title LIKE '%#{params[:q]}%'")
# ✅ Parameterized
Task.where("title LIKE ?", "%#{params[:q]}%")
```
### Anti-Patterns
- ❌ **Fat models** — extract to service objects / form objects / query objects
- ❌ **Business logic in controllers** — controllers are thin routing layers
- ❌ **Callbacks for complex side effects** — use service objects
- ❌ **`default_scope`** — implicit, surprising, hard to override
- ❌ **`update_attribute` (skips validation)** — use `update!`
- ❌ **`rescue Exception`** — catches everything including `SystemExit`, `Interrupt`
- ❌ **String interpolation in SQL** — SQL injection vector
### Testing
> For universal testing principles, see `.agents/rules/testing-strategy.md`. Below: language-specific patterns only.
1. **RSpec (preferred):**
```ruby
RSpec.describe TasksController, type: :request do
describe 'POST /tasks' do
it 'creates a task with valid params' do
post tasks_path, params: { task: { title: 'Test', priority: 'high' } }
expect(response).to have_http_status(:created)
expect(Task.last.title).to eq('Test')
end
it 'returns errors with invalid params' do
post tasks_path, params: { task: { title: '' } }
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
```
2. **FactoryBot** for test data:
```ruby
FactoryBot.define do
factory :task do
title { Faker::Lorem.sentence(word_count: 3) }
priority { %w[low medium high].sample }
association :user
end
end
```
3. **Database Cleaner** for test isolation.
4. **Shoulda Matchers** for model spec shortcuts.
### Formatting and Static Analysis
| Tool | Purpose | Command |
|---|---|---|
| RuboCop + rubocop-rails | Linting | `rubocop --autocorrect` |
| Brakeman | Security | `brakeman --no-pager` |
| `bundle audit` | CVE scanning | `bundle audit check --update` |
| `rails_best_practices` | Code quality | `rails_best_practices .` |
### Related
- Ruby Idioms @.agents/skills/ruby-idioms/SKILL.md
- Database Design Principles @.agents/rules/database-design-principles.md
- Security Principles @.agents/rules/security-principles.md
- API Design Principles @.agents/rules/api-design-principles.md
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.