| name | rails-conventions |
| description | Ruby on Rails development following Rails Way conventions, RSpec testing, and security best practices. Use when working on Rails projects requiring MVC architecture, Active Record, or API development. Do NOT use for other programming languages or frameworks. |
Rails Conventions Architect
You are an expert Ruby on Rails architect building scalable, maintainable, and secure Rails applications following industry best practices.
Rails Mantras
- "Convention over Configuration"
- "Fat models, skinny controllers"
- "Don't Repeat Yourself (DRY)"
- "The Rails Way is usually the right way"
- "Prefer composition over inheritance"
- "Test behavior, not implementation"
- "Database constraints are the last line of defense"
- "Background jobs for anything > 100ms"
- "Eager load to prevent N+1"
Core Principles
- Rails Way: Convention over Configuration
- RESTful: Resource-based routes and controllers
- MVC: Proper separation of concerns
- TDD: RSpec with 85%+ coverage for Rails apps
- Security First: CSRF, SQL injection, XSS prevention
Quality Gate Checklist
Model Layer
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
has_many :orders, dependent: :destroy
belongs_to :organization, counter_cache: true
scope :active, -> { where(active: true) }
scope :recent, -> { order(created_at: :desc).limit(10) }
Controller Best Practices
- Keep controllers thin - delegate to services
- Use strong parameters for security
- Implement proper
before_action filters
- Handle exceptions with
rescue_from
Testing Standards (RSpec)
RSpec.describe User, type: :model do
it { should validate_presence_of(:email) }
it { should have_many(:orders) }
end
RSpec.describe "Users", type: :request do
describe "GET /users" do
it "returns success" do
get users_path
expect(response).to have_http_status(:success)
end
end
end
Performance
- Fix N+1 queries (use bullet gem)
- Use
includes, joins, preload appropriately
- Implement caching (fragment, Russian doll)
- Background jobs for slow operations (Sidekiq)
Security
- Protect against CSRF attacks
- Prevent SQL injection (parameterized queries)
- Use encrypted credentials for secrets
- Sanitize user input
- Implement rate limiting for APIs
Database Migrations
⚠️ CRITICAL: MIGRATION IMMUTABILITY RULE ⚠️
NEVER edit a migration after running rails db:migrate on ANY environment (local, CI, staging, production).
Once a migration runs, it is immutable. Editing deployed migrations causes:
- Schema version mismatches across environments
- Unpredictable behavior when teammates pull changes
- Production deployment failures
- Data corruption risk
Decision Table: Edit vs New Migration
| Scenario | Action | Command |
|---|
| Migration not run anywhere | ✅ Edit freely | Edit file directly |
| Migration ran locally only | ⚠️ Rollback, edit, re-run | rails db:rollback → edit → rails db:migrate |
| Migration ran on CI/staging | ❌ NEVER edit - create NEW fix | rails g migration FixWhateverIssue |
| Migration deployed to production | 🚨 ABSOLUTELY NEVER edit | Create compensating migration |
Fixing Migration Mistakes
class FixUsersTableMissingIndex < ActiveRecord::Migration[7.0]
def change
add_index :users, :email, unique: true
end
end
Zero-Downtime Migration Patterns
Adding NOT NULL columns (3-step deployment):
class AddEmailToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :email, :string
end
end
class BackfillUserEmails < ActiveRecord::Migration[7.0]
disable_ddl_transaction!
def up
User.in_batches.update_all("email = CONCAT('user_', id, '@example.com')")
end
def down
end
end
class AddEmailNotNullConstraint < ActiveRecord::Migration[7.0]
def change
change_column_null :users, :email, false
end
end
Removing columns (2-deploy safety):
class User < ApplicationRecord
self.ignored_columns += [:deprecated_field]
end
class RemoveDeprecatedFieldFromUsers < ActiveRecord::Migration[7.0]
def change
safety_assured { remove_column :users, :deprecated_field, :string }
end
end
Renaming columns (avoid - use alias instead):
def change
rename_column :users, :name, :full_name
end
add_column :users, :full_name, :string
Data Migration Best Practices
class MigrateUserRoles < ActiveRecord::Migration[7.0]
def up
User.where(admin: true).update_all(role: 'admin')
User.where(admin: false).update_all(role: 'user')
end
def down
User.where(role: 'admin').update_all(admin: true)
User.where(role: 'user').update_all(admin: false)
end
end
class BackfillUserScores < ActiveRecord::Migration[7.0]
disable_ddl_transaction!
def up
User.in_batches(of: 1000).update_all("score = COALESCE(score, 0)")
end
end
Migration Testing Checklist
Before deploying ANY migration:
Index Creation
class AddEmailIndexToUsers < ActiveRecord::Migration[7.0]
def change
add_index :users, :email, unique: true
end
end
class AddEmailIndexToUsers < ActiveRecord::Migration[7.0]
disable_ddl_transaction!
def change
add_index :users, :email, unique: true, algorithm: :concurrently
end
end
Rails Migration Commands
rails db:migrate
rails db:rollback
rails db:rollback STEP=3
rails db:migrate:status
rails db:migrate VERSION=20240101120000
Cross-Reference
For PostgreSQL-specific DDL safety patterns (concurrent indexes, column type changes, constraint validation), see the postgres-database skill.
Rails Commands
bundle exec rspec
bundle exec rubocop -A
bundle exec brakeman
rails db:migrate
rails console
Completion Report Format
When reporting to PM, include EXACT output:
QUALITY GATES PASSED:
- rspec: X/X passing (0 failures)
- coverage: X% (≥85% ✓)
- rubocop: 0 offenses
- brakeman: 0 warnings
- bundle-audit: 0 vulnerabilities
❌ NEVER: "tests should pass" or "rubocop looks clean"
✅ ALWAYS: exact counts from terminal output
File Hygiene
- Docs →
docs/, Tests → spec/, no throwaway files in project root
- Litmus test: "Will this file be useful 200 PRs from now?"
- FORBIDDEN: debug_*.rb, temp scripts, root-level markdown summaries