Rails ActiveRecord Reference

ActiveRecord complete reference: migrations, associations, validations, callbacks, named scopes, and query interface patterns.

1. Migrations

# rails generate migration CreateArticles
class CreateArticles < ActiveRecord::Migration[7.1]
  def change
    create_table :articles do |t|
      t.string   :title,    null: false, limit: 300
      t.string   :slug,     null: false, index: { unique: true }
      t.text     :content,  null: false
      t.string   :status,   null: false, default: "draft"
      t.boolean  :featured, default: false
      t.integer  :view_count, default: 0
      t.jsonb    :metadata, default: {}
      t.references :author, null: false, foreign_key: { to_table: :users }
      t.datetime :published_at
      t.timestamps
    end

    add_index :articles, :status
    add_index :articles, [:author_id, :status]
    add_index :articles, :published_at
  end
end

# Add column
class AddRoleToUsers < ActiveRecord::Migration[7.1]
  def change
    add_column :users, :role, :string, default: "user", null: false
    add_index  :users, :role
  end
end

2. Associations

class User < ApplicationRecord
  has_many :articles, foreign_key: :author_id, dependent: :nullify
  has_one  :profile,  dependent: :destroy
  has_many :comments, through: :articles
end

class Article < ApplicationRecord
  belongs_to :author, class_name: "User"
  has_many   :comments,  dependent: :destroy
  has_many   :commenters, through: :comments, source: :user
  has_and_belongs_to_many :tags

  # Polymorphic
  has_many :likes, as: :likeable, dependent: :destroy
end

class Comment < ApplicationRecord
  belongs_to :article
  belongs_to :user
end

class Like < ApplicationRecord
  belongs_to :likeable, polymorphic: true
  belongs_to :user
end

3. Validations

class Article < ApplicationRecord
  validates :title,   presence: true, length: { minimum: 5, maximum: 300 }
  validates :slug,    presence: true, uniqueness: true,
                      format: { with: /\A[a-z0-9\-]+\z/, message: "only lowercase letters, numbers, hyphens" }
  validates :content, presence: true, length: { minimum: 50 }
  validates :status,  inclusion: { in: %w[draft published archived] }
  validates :email,   format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true

  # Conditional
  validates :published_at, presence: true, if: :published?

  # Custom validator
  validate :slug_not_reserved

  private

  def published?
    status == "published"
  end

  def slug_not_reserved
    reserved = %w[admin api auth login logout register]
    errors.add(:slug, "is reserved") if slug.in?(reserved)
  end
end

4. Callbacks

class Article < ApplicationRecord
  before_validation :generate_slug, if: -> { slug.blank? }
  before_save       :sanitize_content
  after_create      :notify_subscribers
  after_destroy     :cleanup_cache

  private

  def generate_slug
    self.slug = title.to_s.parameterize
  end

  def sanitize_content
    self.content = ActionController::Base.helpers.sanitize(content, tags: %w[p b i a ul ol li])
  end

  def notify_subscribers
    NotifySubscribersJob.perform_later(id) if published?
  end

  def cleanup_cache
    Rails.cache.delete("article:#{id}")
  end
end

5. Scopes & Query Interface

class Article < ApplicationRecord
  scope :published,  -> { where(status: "published") }
  scope :featured,   -> { where(featured: true) }
  scope :recent,     -> { order(created_at: :desc) }
  scope :by_author,  ->(user_id) { where(author_id: user_id) }
  scope :search,     ->(q) { where("title ILIKE ?", "%#{q}%") }
end

# Usage
articles = Article.published.featured.recent.page(1).per(20)
articles = Article.published.by_author(current_user.id).search("ruby")

# Query methods
Article.where(status: "published").order(created_at: :desc).limit(10)
Article.where("view_count > ?", 1000).pluck(:id, :title)
Article.includes(:author, :tags).where(tags: { name: "ruby" })
Article.left_joins(:comments).where(comments: { id: nil })  # no comments

# Aggregation
Article.published.count
Article.group(:status).count
Article.published.average(:view_count)

6. Migration Column Types

TypeRubySQL (PostgreSQL)
String:stringCHARACTER VARYING(255)
Text:textTEXT
Integer:integerINTEGER
BigInt:bigintBIGINT
Boolean:booleanBOOLEAN
Float:floatFLOAT
Decimal:decimalDECIMAL(p,s)
DateTime:datetimeTIMESTAMP
JSON:json / :jsonbJSON / JSONB
UUID:uuidUUID