This skill should be used when the user asks about "ActiveRecord", "database queries", "query optimization", "N+1 queries", "eager loading", "associations", "migrations", "database indexes", "SQL performance", "ActiveRecord callbacks", "scopes", or needs guidance on efficient database access patterns in Rails 7+.
Provides production-ready ActiveRecord patterns for Rails 7+ including N+1 query prevention, eager loading, batch processing, and safe migrations. Use when users ask about database queries, optimization, associations, or migrations.
/plugin marketplace add betamatt/claude-plugins/plugin install ruby-on-rails@betamatt-claude-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/migration-safety.mdreferences/query-patterns.mdProduction-focused guidance for ActiveRecord query optimization, associations, migrations, and database best practices for Rails 7+.
Always use includes, preload, or eager_load when accessing associations:
# Bad - N+1 query
Order.all.each { |o| puts o.user.email }
# Good - eager loading
Order.includes(:user).each { |o| puts o.user.email }
# For nested associations
Order.includes(line_items: :product).each do |order|
order.line_items.each { |li| puts li.product.name }
end
When to use each:
includes - Rails decides (usually preload)preload - Separate queries, works with limiteager_load - LEFT OUTER JOIN, needed for filtering# Filtering on association - must use eager_load
Order.eager_load(:line_items)
.where(line_items: { product_id: 123 })
# Bad - loads all columns
User.all.map(&:email)
# Good - loads only needed columns
User.pluck(:email)
# When you need objects with limited columns
User.select(:id, :email, :name)
# Bad - loads all records into memory
User.all.each { |u| u.update(last_contacted_at: Time.current) }
# Good - processes in batches
User.find_each(batch_size: 1000) do |user|
user.update(last_contacted_at: Time.current)
end
# For parallelization
User.in_batches(of: 1000) do |batch|
batch.update_all(last_contacted_at: Time.current)
end
# Bad - loads records to count
User.all.count # Loads all, then counts
User.all.length # Same problem
# Good - database count
User.count # SELECT COUNT(*)
# For checking existence
User.any? # Avoid - can be slow
User.exists? # SELECT 1 LIMIT 1 - fast
User.where(admin: true).exists?
class Order < ApplicationRecord
belongs_to :user, counter_cache: true
belongs_to :created_by, class_name: "User", optional: true
has_many :line_items, dependent: :destroy
has_many :products, through: :line_items
has_one :invoice, dependent: :destroy
# Polymorphic
has_many :comments, as: :commentable
# Self-referential
belongs_to :parent_order, class_name: "Order", optional: true
has_many :child_orders, class_name: "Order", foreign_key: :parent_order_id
end
# Migration
add_column :users, :orders_count, :integer, default: 0, null: false
# Reset existing counts
User.find_each do |user|
User.reset_counters(user.id, :orders)
end
# Model
class Order < ApplicationRecord
belongs_to :user, counter_cache: true
end
# Usage - no query needed
user.orders_count
Define explicitly for complex associations:
class Order < ApplicationRecord
has_many :line_items, inverse_of: :order
end
class LineItem < ApplicationRecord
belongs_to :order, inverse_of: :line_items
end
# Prevents extra queries when building
order = Order.new
order.line_items.build(quantity: 1)
order.line_items.first.order # Same object, no query
class Order < ApplicationRecord
scope :recent, -> { where("created_at > ?", 30.days.ago) }
scope :pending, -> { where(status: :pending) }
scope :completed, -> { where(status: :completed) }
scope :for_user, ->(user) { where(user: user) }
# Composable scopes
scope :recent_pending, -> { recent.pending }
# Scope with default
scope :by_status, ->(status = :pending) { where(status: status) }
end
# Chaining
Order.recent.pending.for_user(current_user)
Use class methods for complex logic:
class Order < ApplicationRecord
def self.search(query)
return all if query.blank?
where("order_number ILIKE ? OR notes ILIKE ?",
"%#{query}%", "%#{query}%")
end
def self.total_revenue
completed.sum(:total)
end
end
class Order < ApplicationRecord
def self.with_high_value_items
line_items_table = LineItem.arel_table
orders_table = arel_table
joins(:line_items)
.where(line_items_table[:price].gt(100))
.distinct
end
end
class AddIndexToOrdersUserIdConcurrently < ActiveRecord::Migration[7.1]
disable_ddl_transaction!
def change
add_index :orders, :user_id, algorithm: :concurrently,
if_not_exists: true
end
end
# Rails 7+ handles this safely
class AddStatusToOrders < ActiveRecord::Migration[7.1]
def change
add_column :orders, :status, :string, default: "pending", null: false
end
end
# Step 1: Stop using column in code (deploy first)
# Step 2: Ignore column
class Order < ApplicationRecord
self.ignored_columns += ["legacy_status"]
end
# Step 3: Remove column (separate deploy)
class RemoveLegacyStatusFromOrders < ActiveRecord::Migration[7.1]
def change
safety_assured { remove_column :orders, :legacy_status }
end
end
# Foreign keys - always index
add_index :orders, :user_id
# Composite index for common queries
add_index :orders, [:user_id, :status]
# Partial index for specific conditions
add_index :orders, :created_at,
where: "status = 'pending'",
name: "index_orders_pending_created_at"
# Unique constraint
add_index :users, :email, unique: true
# GIN index for array/JSON columns
add_index :products, :tags, using: :gin
Order.transaction do
order = Order.create!(order_params)
order.line_items.create!(line_item_params)
InventoryService.deduct(order)
end
Order.transaction do
order.update!(status: :processing)
Order.transaction(requires_new: true) do
# This savepoint can fail independently
PaymentService.charge(order)
rescue PaymentError => e
order.update!(payment_error: e.message)
end
end
# Prevent concurrent processing
Order.with_advisory_lock("order_#{order.id}") do
process_order(order)
end
class Order < ApplicationRecord
# Good: Simple, side-effect free callbacks
before_validation :normalize_email
before_save :set_defaults
private
def normalize_email
self.email = email&.downcase&.strip
end
def set_defaults
self.order_number ||= generate_order_number
end
end
Move business logic to service objects:
# Avoid: Complex callback chains
class Order < ApplicationRecord
after_create :send_confirmation, :update_inventory, :notify_warehouse
end
# Prefer: Explicit service object
class Orders::CreateService
def call(params)
Order.transaction do
order = Order.create!(params)
OrderMailer.confirmation(order).deliver_later
Inventory::DeductService.new(order).call
Warehouse::NotifyJob.perform_later(order.id)
order
end
end
end
# config/database.yml
production:
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
checkout_timeout: 5
reaping_frequency: 10
# config/database.yml
production:
primary:
database: myapp_production
primary_replica:
database: myapp_production
replica: true
# Usage
ActiveRecord::Base.connected_to(role: :reading) do
Order.where(status: :pending).count
end
# Automatic switching
class ApplicationController < ActionController::Base
around_action :switch_to_replica, only: [:index, :show]
private
def switch_to_replica
ActiveRecord::Base.connected_to(role: :reading) { yield }
end
end
For detailed patterns and advanced techniques:
references/query-patterns.md - Complex query patterns, CTEs, window functionsreferences/migration-safety.md - Zero-downtime migration strategiesThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.