rails
Ruby on Rails Development
Collaborating skills
- TDD: skill:
tddfor test-driven development with RSpec in Rails applications - Refactor: skill:
refactorfor improving Rails code while preserving behavior
Rails is a web application framework following MVC architecture. This skill covers core Rails development patterns with progressive disclosure — start here, then dive into reference files for details.
Quick Reference
Essential Commands
rails new myapp # Create new app
rails new myapp --api # API-only app
rails console # Interactive REPL
rails server # Start dev server
rails generate # List generators
rails db:migrate # Run migrations
rails db:rollback # Undo last migration
rails routes # List all routes
rails middleware # List middleware stack
Verification Commands
rails db:migrate:status # Check migration status
rails db:version # Current database version
rails runner "puts Rails.env" # Quick environment check
Server verification: After rails server, visit http://localhost:3000. You should see the Rails welcome page or your app root.
Console verification: After rails console, run ActiveRecord::Base.connection.tables to verify database connectivity.
Conventions
| Concept | Convention |
|---|---|
| Model class | Article (singular, CamelCase) |
| Table name | articles (plural, snake_case) |
| Controller | ArticlesController (plural) |
| Foreign key | article_id (singular_table_id) |
| Primary key | id (auto-generated bigint) |
| Timestamps | created_at, updated_at |
Controllers
Controllers handle HTTP requests and coordinate between models and views.
Basic Structure
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
before_action :set_article, only: %i[show edit update destroy]
def index
@articles = Article.all
end
def show
# @article set by before_action
end
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article, notice: "Article created."
else
render :new, status: :unprocessable_entity
end
end
def update
if @article.update(article_params)
redirect_to @article, notice: "Article updated."
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@article.destroy
redirect_to articles_url, notice: "Article deleted."
end
private
def set_article
@article = Article.find(params[:id])
end
def article_params
params.expect(article: [:title, :body, :status])
end
end
Key Patterns
Strong Parameters — Always filter params:
params.expect(user: [:name, :email])
params.require(:user).permit(:name, :email) # Legacy syntax
Before Actions — Run code before actions:
before_action :require_login, except: %i[index show]
before_action :set_article, only: %i[show edit update destroy]
Respond Formats:
respond_to do |format|
format.html
format.json { render json: @article }
format.xml { render xml: @article }
end
Flash Messages:
redirect_to @article, notice: "Success message"
redirect_to @article, alert: "Error message"
For detailed controller patterns (filters, streaming, authentication), see references/controllers.md.
Models (Active Record)
Active Record provides ORM, mapping Ruby objects to database tables.
Basic Model
# app/models/article.rb
class Article < ApplicationRecord
# Associations
belongs_to :user
has_many :comments, dependent: :destroy
has_and_belongs_to_many :tags
# Validations
validates :title, presence: true, length: { minimum: 5 }
validates :body, presence: true
validates :status, inclusion: { in: %w[draft published archived] }
# Scopes
scope :published, -> { where(status: 'published') }
scope :recent, -> { order(created_at: :desc) }
# Callbacks
before_save :sanitize_content
private
def sanitize_content
self.body = body.strip if body.present?
end
end
CRUD Operations
# Create
article = Article.create(title: "Title", body: "Body")
article = Article.new(title: "Title")
article.save
# Read
Article.all
Article.first
Article.find(1)
Article.find_by(title: "Title")
Article.where(status: 'published').order(created_at: :desc)
Article.joins(:comments).where(comments: { spam: false })
# Update
article.update(title: "New Title")
article.title = "New Title"; article.save
Article.update_all(status: 'archived') # Skip callbacks
# Delete
article.destroy
Article.destroy_all
Article.delete_all # Skip callbacks
Common Validations
validates :email, presence: true, uniqueness: true
validates :age, numericality: { greater_than: 0 }
validates :username, format: { with: /\A[a-zA-Z0-9]+\z/ }
validates :password, length: { minimum: 8 }, confirmation: true
validates :terms, acceptance: true
validates :subdomain, exclusion: { in: %w[www admin] }
Associations
belongs_to :user # Foreign key in this table
has_many :comments, dependent: :destroy # One-to-many
has_one :profile, dependent: :destroy # One-to-one
has_many :tags, through: :taggings # Many-to-many via join
has_and_belongs_to_many :categories # Simple many-to-many
For detailed model patterns (complex associations, callbacks, validations), see references/models.md.
Migrations
Migrations evolve your database schema over time.
Creating Migrations
rails generate migration CreateArticles title:string body:text
rails generate migration AddUserToArticles user:references
rails generate migration AddStatusToArticles status:string
Migration Structure
class CreateArticles < ActiveRecord::Migration[8.1]
def change
create_table :articles do |t|
t.string :title, null: false
t.text :body
t.references :user, foreign_key: true
t.string :status, default: 'draft'
t.timestamps
end
add_index :articles, :title
add_index :articles, [:user_id, :status]
end
end
Common Column Types
t.string :name
t.text :description
t.integer :count
t.bigint :views
t.float :rating
t.decimal :price, precision: 10, scale: 2
t.boolean :active, default: false
t.date :published_on
t.datetime :published_at
t.json :metadata
t.binary :data
t.references :user # Adds user_id column
Reversible Migrations
def change
add_column :articles, :views, :integer, default: 0
# Automatically reversible
end
# For complex changes:
def up
add_column :articles, :status, :string
Article.update_all(status: 'draft')
end
def down
remove_column :articles, :status
end
Migration Workflow with Validation
Step 1: Create and review
rails generate migration AddStatusToArticles status:string
# Review the generated file in db/migrate/
Step 2: Run and verify
rails db:migrate
rails db:migrate:status # Confirm migration ran
git diff db/schema.rb # Verify schema changes
Step 3: If something goes wrong
rails db:rollback # Undo last migration
# Fix the migration file
rails db:migrate # Re-run
Recovery from failed migration:
# If migration failed partway through
rails db:migrate:status # Identify failed migration
rails db:rollback STEP=1 # Roll back specific step
# Or manually fix data/issues, then:
rails db:migrate:redo # Re-run the last migration
For detailed migration patterns (indexes, foreign keys, data migrations), see references/migrations.md.
Routes
Routes map HTTP requests to controller actions.
Resource Routing
# config/routes.rb
Rails.application.routes.draw do
# RESTful resource
resources :articles
# With nested resources
resources :articles do
resources :comments
end
# Only specific actions
resources :articles, only: %i[index show]
# Except specific actions
resources :articles, except: %i[destroy]
# Shallow nesting (avoids deep URLs)
resources :articles do
resources :comments, shallow: true
end
end
RESTful Routes Generated by resources :articles
| HTTP | Path | Action | Helper |
|---|---|---|---|
| GET | /articles | index | articles_path |
| GET | /articles/new | new | new_article_path |
| POST | /articles | create | articles_path |
| GET | /articles/:id | show | article_path(:id) |
| GET | /articles/:id/edit | edit | edit_article_path(:id) |
| PATCH/PUT | /articles/:id | update | article_path(:id) |
| DELETE | /articles/:id | destroy | article_path(:id) |
Non-Resource Routes
# Simple match
get 'about', to: 'pages#about'
# Root route
root 'articles#index'
# With constraints
get 'users/:id', to: 'users#show', constraints: { id: /\d+/ }
# Redirect
get '/blog', to: redirect('/articles')
# Mount engine
mount Sidekiq::Web => '/sidekiq'
For detailed routing patterns (namespaces, concerns, constraints), see references/routes.md.
Active Job (Background Jobs)
Active Job handles background tasks like email sending, data processing.
Creating Jobs
rails generate job process_image
Job Structure
# app/jobs/process_image_job.rb
class ProcessImageJob < ApplicationJob
queue_as :default
def perform(image_id)
image = Image.find(image_id)
# Process the image
end
end
Enqueuing Jobs
# Execute async
ProcessImageJob.perform_later(image.id)
# Execute at specific time
ProcessImageJob.set(wait_until: Date.tomorrow.noon).perform_later(image.id)
# Execute after delay
ProcessImageJob.set(wait: 1.hour).perform_later(image.id)
# Execute immediately (blocks)
ProcessImageJob.perform_now(image.id)
# Bulk enqueue
jobs = users.map { |u| CleanupJob.new(u.id) }
ActiveJob.perform_all_later(jobs)
Job Configuration
class ProcessImageJob < ApplicationJob
queue_as :high_priority
retry_on StandardError, wait: 5.seconds, attempts: 3
discard_on ImageNotFoundError
def perform(image_id)
# ...
end
end
For detailed job patterns (queues, callbacks, retries), see references/active_job.md.
API-Only Applications
Rails can serve JSON APIs without view rendering.
Creating API Apps
rails new myapi --api
API Controller
# app/controllers/api/v1/articles_controller.rb
class Api::V1::ArticlesController < ApplicationController
def index
@articles = Article.all
render json: @articles
end
def create
@article = Article.new(article_params)
if @article.save
render json: @article, status: :created
else
render json: { errors: @article.errors }, status: :unprocessable_entity
end
end
private
def article_params
params.expect(article: [:title, :body])
end
end
API Routes
# config/routes.rb
namespace :api do
namespace :v1 do
resources :articles
end
end
Converting Existing App
# config/application.rb
config.api_only = true
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
end
For detailed API patterns (authentication, versioning, serialization), see references/api.md.
Active Model
Active Model provides model-like behavior without database backing, useful for form objects, service objects, and APIs.
Basic Usage
# app/models/contact_form.rb
class ContactForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :name, :string
attribute :email, :string
attribute :message, :string
validates :name, presence: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :message, length: { minimum: 10 }
def submit
return false unless valid?
ContactMailer.with(form: self).contact_email.deliver_later
true
end
end
Using in Controllers
def create
@form = ContactForm.new(contact_params)
if @form.submit
redirect_to root_path, notice: "Message sent!"
else
render :new, status: :unprocessable_entity
end
end
For detailed Active Model patterns (callbacks, dirty tracking, serialization), see references/active_model.md.
Action Mailer
Action Mailer sends emails from your Rails application.
Creating Mailers
rails generate mailer UserMailer
Mailer Structure
# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
default from: "noreply@example.com"
def welcome_email(user)
@user = user
mail(to: @user.email, subject: "Welcome to MyApp!")
end
end
Sending Email
# Deliver immediately
UserMailer.welcome_email(@user).deliver_now
# Deliver via background job (recommended)
UserMailer.welcome_email(@user).deliver_later
Mailer View
<!-- app/views/user_mailer/welcome_email.html.erb -->
<h1>Welcome, <%= @user.name %>!</h1>
<p>Thanks for joining MyApp.</p>
For detailed mailer patterns (attachments, layouts, previews, multiple formats), see references/action_mailer.md.
Action Mailbox
Action Mailbox routes incoming emails to controller-like mailboxes for processing.
Setup
rails action_mailbox:install
Creating a Mailbox
# app/mailboxes/replies_mailbox.rb
class RepliesMailbox < ApplicationMailbox
def process
# Access email data
subject = mail.subject
sender = mail.from
body = mail.body.decoded
# Create record from email
Comment.create!(
user: User.find_by(email: sender),
body: body
)
end
end
Routing Configuration
# config/routes.rb
Rails.application.routes.draw do
mount ActionMailbox::Engine => "/rails/action_mailbox"
end
# app/mailboxes/application_mailbox.rb
class ApplicationMailbox < ActionMailbox::Base
routing /^replies\+/i => :replies
routing all: :inbox
end
For detailed mailbox patterns (ingress configuration, Exim, Postfix, Sendgrid), see references/action_mailbox.md.
Action Text
Action Text provides rich text editing and storage using the Trix editor.
Setup
rails action_text:install
Adding to Model
# app/models/article.rb
class Article < ApplicationRecord
has_rich_text :content
end
Form Field
<!-- app/views/articles/_form.html.erb -->
<%= form_with model: @article do |form| %>
<%= form.label :content %>
<%= form.rich_text_area :content %>
<% end %>
Displaying Content
<!-- app/views/articles/show.html.erb -->
<%= @article.content %>
Attaching Images
# Images are automatically handled via Active Storage
# Attachments embed directly in rich text
For detailed Action Text patterns (custom attachments, styling, embeds), see references/action_text.md.
Active Storage
Active Storage handles file uploads with cloud storage support.
Setup
rails active_storage:install
rails db:migrate
Adding Attachments
# app/models/user.rb
class User < ApplicationRecord
has_one_attached :avatar
has_many_attached :documents
end
Uploading Files
# Direct assignment
user.avatar.attach(params[:avatar])
# From uploaded file
user.avatar.attach(io: File.open("path/to/file.jpg"), filename: "avatar.jpg")
# From URL
user.avatar.attach(url: "https://example.com/image.jpg")
Displaying Files
<!-- Original -->
<%= image_tag user.avatar %>
<!-- Variant (resized) -->
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %>
<!-- Download link -->
<%= link_to "Download", rails_blob_path(user.document, disposition: "attachment") %>
For detailed Active Storage patterns (direct uploads, variants, cloud providers), see references/active_storage.md.
Action Cable
Action Cable adds real-time WebSockets to your Rails application.
Creating a Channel
rails generate channel Chat
Server-Side Channel
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
def speak(data)
ActionCable.server.broadcast(
"chat_#{params[:room]}",
message: data["message"]
)
end
end
Client-Side Subscription
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create(
{ channel: "ChatChannel", room: "general" },
{
received(data) {
// Handle incoming broadcast
console.log(data.message)
}
}
)
Broadcasting from Controller/Job
ActionCable.server.broadcast("chat_general", { message: "Hello!" })
For detailed Action Cable patterns (authentication, deployment, Redis), see references/action_cable.md.
Internationalization (I18n)
Rails I18n provides translations and localization for multiple languages.
Translation Files
# config/locales/en.yml
en:
hello: "Hello world"
users:
welcome: "Welcome, %{name}!"
Using Translations
<%= t("hello") %>
<%= t("users.welcome", name: @user.name) %>
Setting Locale
# app/controllers/application_controller.rb
around_action :switch_locale
def switch_locale(&action)
I18n.with_locale(locale_from_params, &action)
end
def locale_from_params
params[:locale] || I18n.default_locale
end
Localizing Dates/Numbers
<%= l(Date.today, format: :long) %>
<%= number_to_currency(1234.56) %>
For detailed I18n patterns (pluralization, model translations, date formats), see references/i18n.md.
Configuration
Rails configuration spans application settings, database, security, and environment-specific options.
Application Configuration
# config/application.rb
module MyApp
class Application < Rails::Application
config.time_zone = "Central Time (US & Canada)"
config.i18n.default_locale = :en
config.autoload_paths << Rails.root.join("lib")
end
end
Environment-Specific
# config/environments/production.rb
Rails.application.configure do
config.cache_classes = true
config.eager_load = true
config.force_ssl = true
config.log_level = :info
end
Database Configuration
# config/database.yml
production:
adapter: postgresql
url: <%= ENV["DATABASE_URL"] %>
pool: 5
Credentials
rails credentials:edit
# Access credentials
Rails.application.credentials.secret_key_base
Rails.application.credentials.aws[:access_key_id]
For detailed configuration patterns (security, middleware, multiple databases), see references/configuring.md.
Reference Files
For deep dives into specific topics, read these files:
Core Components
references/controllers.md— Filters, streaming, strong parameters, authenticationreferences/models.md— Associations, callbacks, validations, queryingreferences/migrations.md— Indexes, foreign keys, data migrations, rollbackreferences/routes.md— Namespaces, concerns, constraints, mountingreferences/active_job.md— Queues, retries, callbacks, Solid Queuereferences/api.md— Versioning, serialization, JWT, CORS
Additional Components
references/active_model.md— Active Model API, validations, callbacks, dirty trackingreferences/action_mailer.md— Creating mailers, views, sending email, attachmentsreferences/action_mailbox.md— Routing incoming email, ingress configurationreferences/action_text.md— Rich text content, Trix editor, attachmentsreferences/active_storage.md— File uploads, variants, direct uploads, cloud storagereferences/action_cable.md— WebSockets, channels, broadcasting, real-time featuresreferences/i18n.md— Internationalization, translations, pluralization, locale managementreferences/configuring.md— Application, database, security, environment-specific config
Official Documentation
- Rails Guides: https://guides.rubyonrails.org/
- API Docs: https://api.rubyonrails.org/
- Ruby on Rails: https://rubyonrails.org/
More from mguinada/agent-skills
refactor
TDD-based code refactoring preserving behavior through tests. Use Red-Green-Refactor cycles to apply refactoring patterns one test-verified change at a time. **TRIGGERS**: 'clean up code', 'make code simpler', 'reduce complexity', 'refactor this', 'apply DRY', 'extract method', 'remove duplication'. **DISTINCT FROM**: Adding features (use /tdd) or fixing bugs. **PROACTIVE**: Auto-invoke when test-covered code has complexity (functions >50 lines, high cyclomatic complexity, duplication).
16ai-engineering
Build AI agents and agentic workflows. Use when designing/building/debugging agentic systems: choosing workflows vs agents, implementing prompt patterns (chaining/routing/parallelization/orchestrator-workers/evaluator-optimizer), building autonomous agents with tools, designing ACI/tool specs, or troubleshooting/optimizing implementations. **PROACTIVE ACTIVATION**: Auto-invoke when building agentic applications, designing workflows vs agents, or implementing agent patterns. **DETECTION**: Check for agent code (MCP servers, tool defs, .mcp.json configs), or user mentions of \"agent\", \"workflow\", \"agentic\", \"autonomous\". **USE CASES**: Designing agentic systems, choosing workflows vs agents, implementing prompt patterns, building agents with tools, designing ACI/tool specs, troubleshooting/optimizing agents.
13git-commit
Generate concise, descriptive git commit messages following best practices. Use when creating git commits from staged changes, crafting commit messages, or reviewing commit message quality. Use when the user says /commit or asks to create a git commit. **PROACTIVE ACTIVATION**: Auto-invoke when staged changes detected or user asks to commit/save work. **DETECTION**: Run git status - if staged changes exist, offer to commit. User says \"commit\", \"save\", \"done with feature\". **USE CASES**: Staged changes detected, work completed, user wants to save progress.
12tdd
Guide Test-Driven Development workflow (Red-Green-Refactor) for new features, bug fixes, and refactoring. Supports both Python (pytest) and Ruby (RSpec). Use when writing tests, implementing features, or following TDD methodology. **PROACTIVE ACTIVATION**: Auto-invoke when implementing features or fixing bugs in projects with test infrastructure. **DETECTION**: Check for tests/ directory, pytest.ini, pyproject.toml with pytest config, spec/ directory, .rspec file, or *_spec.rb files. **USE CASES**: Writing production code, fixing bugs, adding features, legacy code characterization.
11create-pr
Creates GitHub pull requests with properly formatted titles, a body matching the project's PR template, and appropriate type/scope labels. Automatically creates labels if they don't exist. Use when creating PRs, submitting changes for review, or when the user says /pr or asks to create a pull request. **PROACTIVE ACTIVATION**: Auto-invoke when a branch has commits ahead of main and the user signals the work is ready. **DETECTION**: Run git log origin/main..HEAD - if commits exist and user signals readiness, offer to open a PR. User says \"open a PR\", \"ready for review\", \"this is done\", \"let's merge\", \"submit this\". **USE CASES**: Feature or fix complete, user finished a series of commits and mentions review or merging.
11prompt-engineering
Creates system prompts, writes tool descriptions, and structures agent instructions for agentic systems. Use when the user asks to create, generate, or design prompts for AI agents, especially for tool-using agents, planning agents, or autonomous systems. **PROACTIVE ACTIVATION**: Auto-invoke when designing prompts for agents, tools, or agentic workflows in AI projects. **DETECTION**: Check for agent/tool-related code, prompt files, or user mentions of \"prompt\", \"agent\", \"LLM\". **USE CASES**: Designing system prompts, tool descriptions, agent instructions, prompt optimization, reducing hallucinations.
10