| name | active-record-db |
| description | This skill should be used when the user asks about Active Record models, database migrations, queries, associations (belongs_to, has_many, has_one, has_and_belongs_to_many), validations, callbacks, scopes, database schema design, SQL optimization, N+1 queries, eager loading, joins, or database-specific features (PostgreSQL, MySQL, SQLite). Also use when discussing ORM patterns, data modeling, or database best practices. Examples: |
Active Record & Databases: Rails ORM Mastery
Overview
Active Record is Rails' Object-Relational Mapping (ORM) layer. It connects Ruby objects to database tables, providing an elegant API for creating, reading, updating, and deleting data without writing SQL.
Active Record embodies Rails philosophy:
- Convention over configuration: Table names, foreign keys, and primary keys follow conventions
- DRY: Schema drives model attributes; no redundant declarations
- Object-oriented: Work with Ruby objects, not raw SQL
- Database agnostic: Same code works with PostgreSQL, MySQL, SQLite
Master Active Record and you master data in Rails applications.
Models and Conventions
Basic Model
A model represents a table and provides domain logic:
class Product < ApplicationRecord
end
Rails infers:
- Table name:
products (pluralized)
- Primary key:
id
- Attributes from schema
- Timestamps:
created_at, updated_at
No configuration needed—just convention.
Naming Conventions
| Element | Convention | Example |
|---|
| Model | Singular, CamelCase | Product, LineItem |
| Table | Plural, snake_case | products, line_items |
| Foreign key | model_id | user_id, category_id |
| Join table | Alphabetical models | orders_products |
| Primary key | id | Auto-generated integer |
Irregular pluralizations work automatically:
Person → people
Child → children
Octopus → octopi
Rails' inflector handles English pluralization rules.
Schema Conventions
Special column names have automatic behavior:
id: Primary key (auto-generated)
created_at: Set when record created
updated_at: Updated when record saved
lock_version: Optimistic locking counter
type: Single Table Inheritance discriminator
{association}_id: Foreign key for associations
{association}_type: Polymorphic association type
Migrations
Migrations are Ruby scripts that modify database schema.
Creating Migrations
rails generate migration CreateProducts name:string price:decimal
rails generate model Product name:string price:decimal
Generates:
class CreateProducts < ActiveRecord::Migration[8.0]
def change
create_table :products do |t|
t.string :name
t.decimal :price, precision: 10, scale: 2
t.timestamps
end
end
end
Running Migrations
rails db:migrate
rails db:rollback
rails db:migrate:status
rails db:migrate VERSION=20240115100000
Migration Methods
Creating tables:
create_table :products do |t|
t.string :name, null: false
t.text :description
t.decimal :price, precision: 10, scale: 2
t.integer :quantity, default: 0
t.boolean :available, default: true
t.references :category, foreign_key: true
t.timestamps
end
Modifying tables:
change_table :products do |t|
t.rename :description, :details
t.change :price, :decimal, precision: 12, scale: 2
t.remove :quantity
t.string :sku
t.index :sku, unique: true
end
Adding columns:
add_column :products, :featured, :boolean, default: false
add_index :products, :name
add_reference :products, :supplier, foreign_key: true
Removing columns:
remove_column :products, :quantity
remove_index :products, :sku
remove_reference :products, :supplier
See references/migrations.md for comprehensive migration patterns.
Associations
Associations define relationships between models.
belongs_to
Declares a one-to-one or many-to-one relationship:
class Product < ApplicationRecord
belongs_to :category
end
has_many
Declares a one-to-many relationship:
class Category < ApplicationRecord
has_many :products
end
has_one
Declares a one-to-one relationship:
class User < ApplicationRecord
has_one :profile
end
has_many :through
Many-to-many with join model:
class Order < ApplicationRecord
has_many :line_items
has_many :products, through: :line_items
end
class LineItem < ApplicationRecord
belongs_to :order
belongs_to :product
end
class Product < ApplicationRecord
has_many :line_items
has_many :orders, through: :line_items
end
has_and_belongs_to_many
Many-to-many without join model:
class Product < ApplicationRecord
has_and_belongs_to_many :tags
end
class Tag < ApplicationRecord
has_and_belongs_to_many :products
end
Polymorphic Associations
One model belongs to multiple model types:
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
class Product < ApplicationRecord
has_many :comments, as: :commentable
end
post.comments.create(body: "Great post!")
product.comments.create(body: "Love this product!")
See references/associations.md for advanced association patterns.
Querying
Active Record provides a rich query interface.
Finding Records
Product.find(1)
Product.find([1, 2, 3])
Product.find_by(name: "Widget")
Product.find_by!(name: "Widget")
Product.first
Product.last
Product.all
Where Queries
Product.where(available: true)
Product.where("price < ?", 10)
Product.where("price BETWEEN ? AND ?", 10, 50)
Product.where(category_id: [1, 2, 3])
Product.where.not(category_id: 1)
Product.where(created_at: 1.week.ago..Time.now)
Product.where("name LIKE ?", "%widget%")
Ordering and Limiting
Product.order(created_at: :desc)
Product.order(price: :asc, name: :asc)
Product.limit(10)
Product.offset(20).limit(10)
Selecting Specific Columns
Product.select(:id, :name, :price)
Product.select("id, name, UPPER(name) as uppercase_name")
Joining Tables
Product.joins(:category)
Product.joins(:category, :tags)
Product.left_outer_joins(:reviews)
Product.joins(:category).where(categories: { name: "Electronics" })
Eager Loading (N+1 Prevention)
Problem (N+1 queries):
products = Product.all
products.each do |product|
puts product.category.name
end
Solution (eager loading):
products = Product.includes(:category).all
products.each do |product|
puts product.category.name
end
Methods:
includes: Preload associations (two queries)
eager_load: Preload with LEFT OUTER JOIN (one query)
preload: Always uses separate queries
Scopes
Reusable query fragments:
class Product < ApplicationRecord
scope :available, -> { where(available: true) }
scope :cheap, -> { where("price < ?", 10) }
scope :expensive, -> { where("price > ?", 100) }
scope :in_category, ->(category) { where(category: category) }
end
Product.available.cheap
Product.expensive.in_category("Electronics")
Method Chaining
Build complex queries incrementally:
products = Product.all
products = products.where(available: true) if params[:available]
products = products.where(category: params[:category]) if params[:category]
products = products.where("price < ?", params[:max_price]) if params[:max_price]
products = products.order(params[:sort] || :created_at)
products = products.page(params[:page])
products
Validations
Ensure data integrity before saving.
Common Validations
class Product < ApplicationRecord
validates :name, presence: true
validates :price, numericality: { greater_than: 0 }
validates :sku, uniqueness: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :description, length: { minimum: 10, maximum: 500 }
validates :category, presence: true
validates :terms, acceptance: true
end
Conditional Validations
validates :coupon_code, presence: true, if: :coupon_used?
validates :shipping_address, presence: true, unless: :pickup?
Custom Validations
validate :price_must_be_reasonable
private
def price_must_be_reasonable
if price.present? && price > 10000
errors.add(:price, "is unreasonably high")
end
end
Validation Helpers
product.valid?
product.errors.full_messages
product.save
product.save!
product.save(validate: false)
Callbacks
Run code at specific points in an object's lifecycle.
Common Callbacks
class Product < ApplicationRecord
before_validation :normalize_name
after_validation :log_errors
before_save :calculate_discount
after_save :clear_cache
before_create :generate_sku
after_create :notify_team
before_update :track_price_changes
after_update :reindex_search
before_destroy :check_orders
after_destroy :cleanup_images
after_commit :sync_to_external_system
private
def normalize_name
self.name = name.strip.titleize if name.present?
end
def generate_sku
self.sku = SecureRandom.hex(8).upcase
end
def check_orders
throw :abort if orders.exists?
end
end
Callback Order
before_validation
after_validation
before_save
before_create / before_update
- Database operation
after_create / after_update
after_save
after_commit / after_rollback
Skipping Callbacks
product.update_columns(price: 9.99)
product.update_attribute(:price, 9.99)
product.increment!(:view_count)
Advanced Patterns
Single Table Inheritance (STI)
class Vehicle < ApplicationRecord
end
class Car < Vehicle
end
class Truck < Vehicle
end
Car.all
Vehicle.all
Enums
class Order < ApplicationRecord
enum status: [:pending, :processing, :shipped, :delivered, :cancelled]
end
order = Order.create!(status: :pending)
order.pending?
order.processing!
order.processing?
Order.pending
Order.not_pending
Composite Primary Keys (Rails 8)
class BookOrder < ApplicationRecord
self.primary_key = [:book_id, :order_id]
belongs_to :book
belongs_to :order
end
BookOrder.find([book_id, order_id])
Database-Specific Features
PostgreSQL
add_column :products, :metadata, :jsonb, default: {}
product.metadata = { color: "red", size: "large" }
Product.where("metadata->>'color' = ?", "red")
add_column :products, :tags, :string, array: true, default: []
product.tags = ["electronics", "sale"]
Product.where("? = ANY(tags)", "electronics")
Product.where("to_tsvector('english', name) @@ to_tsquery(?)", "widget")
MySQL-Specific
Product.where("name = ?", "Widget")
add_column :products, :settings, :json
Best Practices
- Use scopes for reusable queries
- Eager load to prevent N+1 queries
- Add indexes for foreign keys and frequently queried columns
- Validate before saving to maintain data integrity
- Use transactions for multi-step operations
- Limit callbacks - keep them simple and focused
- Use migrations - never modify schema directly
- Test validations and associations
- Profile queries - use
explain to optimize
- Use database constraints (NOT NULL, UNIQUE, FOREIGN KEY)
Further Reading
For deeper exploration:
references/migrations.md: Complete migration guide with patterns
references/associations.md: Advanced association techniques
references/query-optimization.md: Performance tuning and N+1 prevention
For code examples:
examples/active-record-patterns.rb: Common Active Record patterns
Summary
Active Record provides:
- Models that represent database tables
- Migrations for schema changes
- Associations for relationships
- Validations for data integrity
- Queries without writing SQL
- Callbacks for lifecycle hooks
- Conventions that eliminate configuration
Master Active Record, and you master data in Rails.