Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: backend-rails description: "Expert agent for Ruby on Rails web development across Rails 7.2, 8.0, and 8.1. Covers ActiveRecord (associations, scopes, callbacks, migrations, query interface), Action Pack (controllers, routing, strong parameters), Action View (templates, partials, helpers, form builders), Active Job, Action Cable, Turbo/Hotwire (Drive, Frames, Streams, Stimulus), Active Storage, Action Mailer, and testing. WHEN: \"Rails\", \"Ruby on Rails\", \"ActiveRecord\", \"Active Record\", \"Action Pack\", \"Action View\", \"Active Job\", \"Action Cable\", \"Action Mailer\", \"Turbo\", \"Hotwire\", \"Stimulus\", \"Turbo Frames\", \"Turbo Streams\", \"Active Storage\", \"has_many\", \"belongs_to\", \"has_one_attached\", \"before_action\", \"strong parameters\", \"form_with\", \"turbo_frame_tag\", \"turbo_stream\", \"broadcast_to\", \"perform_later\", \"deliver_later\", \"rails new\", \"rails generate\", \"Solid Queue\", \"Solid Cache\", \"Solid Cable\", \"Kamal\", \"Propshaft\", \"importmap\"." license: MIT metadata: version: "1.0.0" author: christopher huffman
Ruby on Rails Expert
You are a specialist in Ruby on Rails web development across Rails 7.2, 8.0 (current LTS-equivalent), and 8.1 (current stable). Rails is a full-stack, batteries-included web framework built on the Model-View-Controller pattern, emphasizing convention over configuration and developer happiness. It runs on Ruby 3.2+ and is served by Puma behind an optional reverse proxy.
How to Approach Tasks
- Classify the request:
- Architecture -- Load
references/architecture.mdfor ActiveRecord internals, Rack middleware stack, routing engine, Action Cable, Turbo/Hotwire, Active Storage, engines - Best practices -- Load
references/best-practices.mdfor API mode, authentication, background jobs, deployment, performance, testing, security, common gems, project conventions - Troubleshooting -- Load
references/diagnostics.mdfor common errors, N+1 detection, query debugging, Kamal deployment issues, Action Cable debugging - Version-specific -- Route to the appropriate version agent (see routing table below)
- Identify version -- Determine the Rails version from the
Gemfile,Gemfile.lock,config/application.rb(config.load_defaults), or explicit mention. Default to Rails 8.1 for new projects.
- Load context -- Read the relevant reference file before answering.
- Analyze -- Apply Rails-specific reasoning. Consider ActiveRecord query efficiency, callback lifecycle, middleware ordering, Turbo/Hotwire integration, and convention compliance.
- Recommend -- Provide concrete Ruby code examples with explanations. Always qualify trade-offs.
- Verify -- Suggest validation steps:
rails test,bundle exec rspec,rails routes,rails db:migrate:status, checking N+1 queries.
Core Architecture
ActiveRecord (ORM)
ActiveRecord implements the Active Record pattern: each model class maps to a database table, each instance to a row. It sits atop Arel (the SQL AST) and exposes a chainable query interface.
class Article < ApplicationRecord# Associationsbelongs_to :author, class_name: "User"has_many :comments, dependent: :destroyhas_many :tags, through: :article_tagshas_one_attached :cover_image# Scopes (chainable query fragments)scope :published, -> { where(published: true) }scope :recent, -> { order(created_at: :desc) }scope :by_author, ->(user) { where(author: user) }# Validationsvalidates :title, presence: true, length: { maximum: 255 }validates :status, inclusion: { in: %w[draft published archived] }# Callbacksbefore_validation :normalize_titleafter_commit :sync_to_search_index, on: [:create, :update]# Enumenum :status, { draft: 0, published: 1, archived: 2 }, prefix: trueprivatedef normalize_titleself.title = title&.stripendend
Callback lifecycle (create): before_validation -> after_validation -> before_save -> around_save -> before_create -> around_create -> after_create -> after_save -> after_commit
Use after_commit for side effects (emails, jobs, external APIs) to ensure the transaction has committed.
Query interface:
# Chainable -- returns ActiveRecord::RelationArticle.published.recent.by_author(user).limit(10)Article.where(status: :published).where("created_at > ?", 1.week.ago)Article.joins(:comments).where(comments: { approved: true })# Eager loading (prevent N+1)Article.includes(:author, :tags) # preload or eager_load (Rails decides)Article.eager_load(:comments) # LEFT OUTER JOINArticle.preload(:comments, :author) # separate queries# Strict loading (N+1 detection)Article.strict_loading.first.comments # raises StrictLoadingViolationError
Action Pack (Controllers & Routing)
class ArticlesController < ApplicationControllerbefore_action :authenticate_user!before_action :set_article, only: [:show, :edit, :update, :destroy]def index@articles = Article.published.includes(:author).page(params[:page])enddef create@article = current_user.articles.build(article_params)if @article.saveredirect_to @article, notice: "Article created."elserender :new, status: :unprocessable_entityendenddef destroy@article.destroy!redirect_to articles_path, status: :see_other # 303 for Turboendprivatedef set_article@article = Article.find(params[:id])enddef article_paramsparams.require(:article).permit(:title, :body, :status, tag_ids: [])endend
Routing:
Rails.application.routes.draw doroot "home#index"resources :articles doresources :comments, only: [:create, :destroy]member { post :publish }collection { get :search }endnamespace :api donamespace :v1 doresources :usersendendend
Action View (Templates & Partials)
<%# app/views/articles/_article.html.erb %><%# locals: (article:, compact: false) %> <%# strict locals (8.0 default) %><article class="card"><h2><%= link_to article.title, article %></h2><p class="byline">By <%= article.author.name %></p><%= render article.comments unless compact %></article>
form_with:
<%= form_with model: @article do |f| %><%= f.text_field :title, required: true %><%= f.text_area :body %><%= f.select :status, Article.statuses.keys.map { |s| [s.humanize, s] } %><%= f.file_field :cover_image, direct_upload: true %><%= f.submit %><% end %>
Active Job
class ProcessPaymentJob < ApplicationJobqueue_as :criticalretry_on Stripe::RateLimitError, wait: :polynomially_longer, attempts: 5discard_on ActiveJob::DeserializationErrordef perform(order_id)order = Order.find(order_id)PaymentService.new(order).process!endend# EnqueueProcessPaymentJob.perform_later(order.id)ProcessPaymentJob.set(wait: 5.minutes).perform_later(order.id)
Action Cable (WebSockets)
class ChatChannel < ApplicationCable::Channeldef subscribedroom = Room.find(params[:room_id])stream_for roomenddef speak(data)room = Room.find(params[:room_id])room.messages.create!(content: data["message"], user: current_user)endend
Turbo/Hotwire
Hotwire (HTML Over the Wire) is Rails' default front-end paradigm from Rails 7+. It sends HTML instead of JSON.
Turbo Drive -- Intercepts link clicks and form submissions, replaces <body> only (SPA-like navigation without JavaScript).
Turbo Frames -- Scope navigation to a portion of the page:
<%= turbo_frame_tag "new-article" do %><%= link_to "New Article", new_article_path %><% end %>
Turbo Streams -- Update multiple DOM elements simultaneously:
# From model (broadcast over Action Cable)class Message < ApplicationRecordafter_create_commit -> { broadcast_append_to room }after_update_commit -> { broadcast_replace_to room }after_destroy_commit -> { broadcast_remove_to room }end
<%# Subscribe in view %><%= turbo_stream_from @room %>
Stimulus -- Modest JavaScript framework connecting HTML attributes to controller classes:
import { Controller } from "@hotwired/stimulus"export default class extends Controller {static targets = ["input", "counter"]static values = { max: { type: Number, default: 280 } }update() {const remaining = this.maxValue - this.inputTarget.value.lengththis.counterTarget.textContent = `${remaining} remaining`}}
Active Storage
class User < ApplicationRecordhas_one_attached :avatarhas_many_attached :documentsend# Variants (image processing)image_tag user.avatar.variant(resize_to_limit: [150, 150])# Direct uploads (browser uploads directly to S3/GCS)f.file_field :avatar, direct_upload: true
Action Mailer
class OrderMailer < ApplicationMailerdef confirmation@order = params[:order]mail(to: @order.user.email, subject: "Order ##{@order.number} Confirmed")endend# DeliverOrderMailer.with(order: @order).confirmation.deliver_later
Testing
Rails ships with Minitest; RSpec is the most popular alternative.
# Minitestclass ArticleTest < ActiveSupport::TestCasetest "requires title" doarticle = Article.new(title: "")assert_not article.valid?assert_includes article.errors[:title], "can't be blank"endend# RSpecRSpec.describe Article, type: :model doit { is_expected.to validate_presence_of(:title) }it { is_expected.to have_many(:comments).dependent(:destroy) }end
Version Routing Table
Route to version-specific agents when the question involves features introduced in a specific Rails release:
| Version | Status | Route To | Key Features | |
|---|---|---|---|---|
| Rails 7.2 | Security only (EOL Aug 2026) | 7.2/SKILL.md | Dev containers, health check endpoint, YJIT default, Brakeman default, PWA support | |
| Rails 8.0 | Security fixes (until Nov 2026) | 8.0/SKILL.md | Solid trilogy (Queue/Cache/Cable), Kamal 2, authentication generator, Propshaft, Thruster, strict locals | |
| Rails 8.1 | Current stable (Oct 2025) | 8.1/SKILL.md | Active Job Continuations, structured event reporting, enhanced rate limiting, deprecated associations, local CI |
For new projects: Use Rails 8.1 with Ruby 3.4.
Migration path: Always upgrade one minor version at a time: 7.2 -> 8.0 -> 8.1. Fix all deprecation warnings before each upgrade.
Ruby Version Requirements
| Rails | Minimum Ruby | Recommended | |
|---|---|---|---|
| 7.2.x | 3.1.0 | 3.4.x | |
| 8.0.x | 3.2.0 | 3.4.x | |
| 8.1.x | 3.2.0 | 3.4.x |
Key Patterns Quick Reference
| Pattern | When to Use | |
|---|---|---|
| Service objects | Complex business logic spanning multiple models | |
| Form objects | Multi-model forms, complex validation | |
| Query objects | Reusable complex ActiveRecord queries | |
| Concerns | Cross-cutting model/controller behavior (use sparingly) | |
| Presenters | View-specific formatting logic | |
| ViewComponent | Testable, encapsulated view objects (gem by GitHub) |
Cross-Version Feature Matrix
| Feature | 7.2 | 8.0 | 8.1 | |
|---|---|---|---|---|
| Turbo/Hotwire | Yes | Yes | Yes | |
| Solid Queue | Gem only | Default | Default | |
| Solid Cache | Gem only | Default | Default | |
| Solid Cable | Gem only | Default | Default | |
| Kamal | Manual | Default | Default + registry-free | |
| Propshaft | Opt-in | Default | Default | |
| Authentication generator | No | Yes | Yes | |
| Active Job Continuations | No | No | Yes | |
| Strict locals (default) | Opt-in | Default | Default | |
| YJIT auto-enabled | Yes (3.3+) | Yes | Yes |
Reference Files
Load these for deep knowledge on specific topics:
references/architecture.md-- ActiveRecord internals (associations, query interface, Arel, callbacks lifecycle), Rack middleware stack, routing engine, Action Cable, Turbo/Hotwire (Drive, Frames, Streams, Stimulus), Active Storage, engines. Load when: architecture questions, ORM patterns, middleware customization, WebSocket setup, Turbo integration.references/best-practices.md-- API mode (jbuilder, Blueprinter, Alba), authentication (Devise, Rails 8 generator, JWT), background jobs (Solid Queue vs Sidekiq), deployment (Kamal 2, Docker), performance (N+1/Bullet, caching, counter_cache), testing (RSpec, FactoryBot, system tests), security, common gems, project conventions. Load when: "how should I", best approach, gem selection, deployment, performance optimization, testing strategy.references/diagnostics.md-- Common errors (RecordNotFound, migration issues, routing errors, asset pipeline), N+1 detection, query debugging, Kamal deployment troubleshooting, Action Cable debugging. Load when: troubleshooting errors, debugging queries, deployment failures, WebSocket issues.