This skill should be used when the user asks to "write a migration", "add a column", "add column to table", "create an index", "add a foreign key", "set up associations", "fix N+1 queries", "optimize queries", "add validations", "create callbacks", "use eager loading", or mentions ActiveRecord, belongs_to, has_many, has_one, :through associations, polymorphic associations, inverse_of, touch: true, counter_cache, dependent: destroy, where clauses, scopes, includes, preload, eager_load, joins, find_each, batch processing, counter caches, foreign key constraints, or database constraints. Should also be used when editing *_model.rb files, working in app/models/ directory, db/migrate/, discussing query performance, N+1 prevention, validation vs constraint decisions, or reviewing database schema design.
/plugin marketplace add hoblin/claude-ruby-marketplace/plugin install activerecord@claude-ruby-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
examples/associations/association_extensions.rbexamples/associations/basic_associations.rbexamples/associations/counter_caches.rbexamples/associations/polymorphic_associations.rbexamples/associations/self_referential.rbexamples/associations/through_associations.rbexamples/basics/crud_operations.rbexamples/basics/dirty_tracking.rbexamples/basics/inheritance.rbexamples/basics/type_casting.rbexamples/callbacks/alternatives_to_callbacks.rbexamples/callbacks/conditional_callbacks.rbexamples/callbacks/lifecycle_callbacks.rbexamples/callbacks/transaction_callbacks.rbexamples/migrations/indexes_and_constraints.rbexamples/migrations/reversible_patterns.rbexamples/migrations/safe_patterns.rbexamples/migrations/schema_changes.rbexamples/querying/batch_processing.rbexamples/querying/eager_loading.rbThis skill provides comprehensive guidance for working with ActiveRecord in Rails applications. Use for writing migrations, defining associations, optimizing queries, preventing N+1 issues, implementing validations, and following database best practices.
# Create
user = User.create(name: "Alice", email: "alice@example.com")
user = User.create!(...) # Raises on failure
# Read
User.find(1) # Raises RecordNotFound
User.find_by(email: "x") # Returns nil if not found
User.where(active: true) # Returns Relation
# Update
user.update(name: "Bob")
user.update!(...) # Raises on failure
# Delete (callbacks run)
user.destroy
# Delete (no callbacks)
user.delete
| Concept | Purpose |
|---|---|
belongs_to | Child side of association (has foreign key) |
has_many / has_one | Parent side of association |
has_many :through | Many-to-many via join model |
includes / preload | Eager loading (prevent N+1) |
scope | Named query builder |
validates | Model-level data validation |
before_save / after_commit | Lifecycle callbacks |
Need to access associated data?
├── NO → Use `joins` (filtering only)
└── YES → Need to filter/sort by association?
├── NO → Use `preload` (separate queries)
└── YES → Large dataset with many associations?
├── YES → Use `includes` with `references`
└── NO → Use `eager_load` (single JOIN)
| Method | Strategy | Best For |
|---|---|---|
includes | Auto-choose | Default choice |
preload | Separate queries | Large datasets, no filtering |
eager_load | LEFT OUTER JOIN | Filtering by association |
joins | INNER JOIN | Filtering only, not accessing data |
# N+1 problem
Post.all.each { |p| p.author.name } # 1 + N queries
# Solution
Post.includes(:author).each { |p| p.author.name } # 2 queries
Does the rule ALWAYS apply, regardless of business logic?
├── Yes → Database constraint
│ └── Examples: NOT NULL, foreign keys, unique emails
└── No → Model validation
└── Examples: Format rules that change, conditional requirements
Need helpful user-facing error messages?
├── Yes → Model validation (possibly WITH constraint)
└── No → Constraint alone is fine
Best Practice: Use both for critical fields:
# Migration (data integrity)
add_index :users, :email, unique: true
# Model (user feedback)
validates :email, presence: true, uniqueness: true
class Author < ApplicationRecord
has_many :books, dependent: :destroy
has_one :profile
end
class Book < ApplicationRecord
belongs_to :author # Required by default
belongs_to :publisher, optional: true # Allow NULL
end
class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
class Appointment < ApplicationRecord
belongs_to :physician
belongs_to :patient
# Join model can have attributes
validates :scheduled_at, presence: true
end
| Option | Purpose |
|---|---|
inverse_of | Required with custom foreign_key |
dependent: :destroy | Cascade delete with callbacks |
counter_cache: true | Cache association count |
touch: true | Update parent's updated_at |
# Always reversible
add_column :users, :name, :string
add_index :users, :email, unique: true
add_reference :orders, :user, foreign_key: true
# Concurrent index (no table lock)
disable_ddl_transaction!
add_index :users, :email, algorithm: :concurrently
remove_column :users, :legacy_field, :string # Include type!
change_column_default :users, :status, from: nil, to: "active"
before_validation → after_validation →
before_save → around_save → before_create →
around_create → [INSERT] → after_create →
after_save → [COMMIT] → after_commit
# WRONG - Race condition!
after_save :enqueue_processing
# CORRECT - Runs after COMMIT
after_commit :enqueue_processing, on: :create
# BAD - loads all records
User.all.each { |u| process(u) }
# GOOD - processes in batches
User.find_each { |u| process(u) }
# Bulk operations
User.where(old: true).in_batches.update_all(archived: true)
includes to prevent N+1 queriesfind_each for processing large datasetspluck(:column) instead of all.map(&:column)inverse_of when using custom foreign_keyafter_commit for background jobs and external APIshas_many :through over has_and_belongs_to_manydefault_scope (causes subtle issues)delete when you need callbacksupdate_column to bypass validations casuallyafter_save for external system interactions| Anti-Pattern | Solution |
|---|---|
| N+1 queries | Use includes, preload, or eager_load |
User.all.map(&:email) | Use User.pluck(:email) |
| Uniqueness without index | Add unique database index |
validates :active, presence: true | Use inclusion: { in: [true, false] } for booleans |
after_save for jobs | Use after_commit |
| Callback hell | Extract to service objects |
default_scope | Use explicit scopes |
has_and_belongs_to_many | Use has_many :through |
For detailed patterns and complete API references, consult:
references/basics.md - Conventions, CRUD, dirty tracking, STI, type castingreferences/migrations.md - Schema changes, indexes, constraints, safe patternsreferences/validations.md - Built-in validators, custom validators, contextsreferences/callbacks.md - Lifecycle hooks, transaction callbacks, alternativesreferences/associations.md - All association types, inverse_of, dependent optionsreferences/querying.md - Finders, eager loading, scopes, batch processingReady-to-use code patterns in examples/:
examples/basics/ - CRUD, dirty tracking, type casting, inheritanceexamples/migrations/ - Schema changes, indexes, safe patterns, reversibilityexamples/validations/ - Built-in, conditional, custom, contexts, constraintsexamples/callbacks/ - Lifecycle, transaction callbacks, conditional, alternativesexamples/associations/ - Basic, through, polymorphic, self-referential, extensionsexamples/querying/ - Finders, eager loading, scopes, batch processing, optimizationCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
This skill should be used when the user asks to "create a hookify rule", "write a hook rule", "configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.