Active Record

Active Record is the ORM of Ruby on Rails, implementing the Active Record pattern with minimal configuration through the Convention over Configuration philosophy. It includes built-in database migrations, validations, callbacks, and association features.

ORMRubyRailsActive RecordWeb Development

GitHub Overview

rails/rails

Ruby on Rails

Stars57,276
Watchers2,311
Forks21,912
Created:April 11, 2008
Language:Ruby
License:MIT License

Topics

activejobactiverecordframeworkhtmlmvcrailsruby

Star History

rails/rails Star History
Data as of: 8/13/2025, 01:43 AM

Library

Active Record

Overview

Active Record is the ORM of Ruby on Rails, implementing the Active Record pattern with minimal configuration through the Convention over Configuration philosophy. It includes built-in database migrations, validations, callbacks, and association features.

Details

Active Record faithfully implements the Active Record pattern defined by Martin Fowler. It embodies Rails' "convention over configuration" philosophy, enabling database operations with minimal code by following conventions for table and column naming. It integrates business logic and data access functionality in model classes, providing an intuitive and expressive API.

Key Features

  • Convention over Configuration: Minimizing configuration through conventions
  • Rich Validations: Validation features to maintain data integrity
  • Associations: Intuitive relationship definitions
  • Callbacks: Hooks in model lifecycle
  • Migrations: Safe schema change management

Pros and Cons

Pros

  • Natural and readable API for Ruby developers
  • Achieving powerful database operations with minimal configuration
  • Rich association features enable concise expression of complex data relationships
  • Complete integration with Rails ecosystem
  • Stability and reliability through years of proven track record

Cons

  • Performance challenges in large-scale systems
  • Tight coupling of business logic and data access due to Active Record pattern
  • Difficult to use with other frameworks due to Rails framework dependency
  • Limited expression for complex queries in some cases

Reference Pages

Code Examples

Installation and Basic Setup

# Create Rails project
rails new myapp
cd myapp

# Database setup (configured in config/database.yml)
rails generate model User name:string email:string
rails generate model Post title:string content:text user:references
# config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: myapp_development
  username: myapp
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>

test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  database: myapp_production
  username: myapp
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>

Basic CRUD Operations (Model Definition, Create, Read, Update, Delete)

# app/models/user.rb
class User < ApplicationRecord
  has_many :posts, dependent: :destroy
  
  validates :name, presence: true, length: { minimum: 2, maximum: 50 }
  validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  
  before_save :downcase_email
  
  scope :active, -> { where(status: 'active') }
  scope :recent, -> { order(created_at: :desc) }
  
  private
  
  def downcase_email
    self.email = email.downcase
  end
end

# app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
  
  validates :title, presence: true, length: { minimum: 5, maximum: 100 }
  validates :content, presence: true, length: { minimum: 10 }
  
  scope :published, -> { where.not(published_at: nil) }
  scope :by_date, -> { order(created_at: :desc) }
  
  def published?
    published_at.present?
  end
end

# Run migrations
# rails db:migrate

# Basic CRUD operations
# Create
user = User.create(
  name: 'John Doe',
  email: '[email protected]'
)

# Read
all_users = User.all
user_by_id = User.find(1)
user_by_email = User.find_by(email: '[email protected]')
active_users = User.active

# Update
user = User.find(1)
user.update(name: 'John Smith')

# Bulk update
User.where(status: 'pending').update_all(status: 'active')

# Delete
user = User.find(1)
user.destroy

# Bulk delete
User.where(status: 'inactive').destroy_all

Advanced Queries and Relationships

# Complex conditional queries
active_users = User.where(status: 'active')
                   .where('created_at > ?', 30.days.ago)
                   .where('email LIKE ?', '%@company.com')
                   .order(created_at: :desc)
                   .limit(10)

# Association queries
users_with_posts = User.includes(:posts)

users_with_recent_posts = User.joins(:posts)
                              .where(posts: { created_at: 1.week.ago.. })
                              .distinct

# Nested associations
users_with_published_posts = User.joins(posts: :comments)
                                 .where(posts: { published_at: ..Time.current })
                                 .where(comments: { approved: true })

# Aggregation queries
user_stats = User.select('COUNT(*) as total_users, AVG(posts_count) as avg_posts')
                 .joins(:posts)
                 .group('users.id')

# Count queries
User.joins(:posts).group('users.id').count
Post.group(:user_id).count

# Subqueries
active_users = User.where(id: Post.select(:user_id).where('created_at > ?', 1.month.ago))

# Complex scopes
class User < ApplicationRecord
  scope :with_recent_activity, ->(days = 30) {
    joins(:posts).where(posts: { created_at: days.days.ago.. }).distinct
  }
  
  scope :by_post_count, ->(min_count = 1) {
    joins(:posts).group('users.id').having('COUNT(posts.id) >= ?', min_count)
  }
end

# Using scopes
active_productive_users = User.active
                               .with_recent_activity(7)
                               .by_post_count(3)

# Dynamic queries
def search_users(params)
  scope = User.all
  scope = scope.where('name ILIKE ?', "%#{params[:name]}%") if params[:name].present?
  scope = scope.where(status: params[:status]) if params[:status].present?
  scope = scope.where('created_at >= ?', params[:created_after]) if params[:created_after].present?
  scope
end

Migrations and Schema Management

# Create migrations
rails generate migration CreateUsers name:string email:string:index
rails generate migration AddStatusToUsers status:string

# Run migrations
rails db:migrate

# Rollback
rails db:rollback
rails db:rollback STEP=3

# Check migration status
rails db:migrate:status
# db/migrate/xxxx_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :name, null: false
      t.string :email, null: false
      t.string :status, default: 'active'
      
      t.timestamps
    end
    
    add_index :users, :email, unique: true
    add_index :users, [:status, :created_at]
  end
end

# db/migrate/xxxx_create_posts.rb
class CreatePosts < ActiveRecord::Migration[7.0]
  def change
    create_table :posts do |t|
      t.string :title, null: false
      t.text :content
      t.references :user, null: false, foreign_key: true
      t.datetime :published_at
      
      t.timestamps
    end
    
    add_index :posts, [:user_id, :published_at]
    add_index :posts, :published_at
  end
end

# Custom migration
class AddFullTextSearchToPosts < ActiveRecord::Migration[7.0]
  def up
    execute "CREATE INDEX posts_full_text_search ON posts USING gin(to_tsvector('english', title || ' ' || content))"
  end
  
  def down
    execute "DROP INDEX posts_full_text_search"
  end
end

# Data migration
class MigrateUserStatuses < ActiveRecord::Migration[7.0]
  def up
    User.where(last_login_at: nil).update_all(status: 'inactive')
    User.where('last_login_at < ?', 1.year.ago).update_all(status: 'dormant')
  end
  
  def down
    User.where(status: ['inactive', 'dormant']).update_all(status: 'active')
  end
end

Performance Optimization and Advanced Features

# Transactions
ActiveRecord::Base.transaction do
  user = User.create!(
    name: 'Jane Doe',
    email: '[email protected]'
  )
  
  post = Post.create!(
    title: 'First Post',
    content: 'My first post using Active Record',
    user: user,
    published_at: Time.current
  )
end

# Batch operations
users_data = [
  { name: 'User 1', email: '[email protected]' },
  { name: 'User 2', email: '[email protected]' },
  { name: 'User 3', email: '[email protected]' }
]

User.insert_all(users_data)

# Batch update
User.where(status: 'pending').update_all(
  status: 'active',
  activated_at: Time.current
)

# Batch processing (find_each)
User.where(status: 'active').find_each(batch_size: 100) do |user|
  user.send_newsletter
end

# Avoid N+1 problem with includes
posts = Post.includes(:user, :comments).limit(10)

posts.each do |post|
  puts "#{post.title} by #{post.user.name} (#{post.comments.count} comments)"
end

# Counter cache
class User < ApplicationRecord
  has_many :posts, dependent: :destroy, counter_cache: true
end

class Post < ApplicationRecord
  belongs_to :user, counter_cache: true
end

# Custom validation
class User < ApplicationRecord
  validate :email_domain_allowed
  
  private
  
  def email_domain_allowed
    allowed_domains = ['company.com', 'partner.org']
    domain = email.split('@').last
    
    unless allowed_domains.include?(domain)
      errors.add(:email, 'must be from an allowed domain')
    end
  end
end

# Callbacks
class User < ApplicationRecord
  before_validation :normalize_email
  before_create :generate_uuid
  after_create :send_welcome_email
  after_update :log_changes, if: :saved_change_to_email?
  
  private
  
  def normalize_email
    self.email = email.strip.downcase
  end
  
  def generate_uuid
    self.uuid = SecureRandom.uuid
  end
  
  def send_welcome_email
    UserMailer.welcome(self).deliver_later
  end
  
  def log_changes
    Rails.logger.info "User #{id} email changed from #{email_previous_change[0]} to #{email}"
  end
end

# Custom attributes
class User < ApplicationRecord
  attribute :preferences, :json, default: {}
  
  def full_name
    "#{first_name} #{last_name}"
  end
  
  def full_name=(name)
    split = name.split(' ', 2)
    self.first_name = split.first
    self.last_name = split.last
  end
end

Framework Integration and Practical Examples

# Controller integration
class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]
  
  def index
    @users = User.includes(:posts)
                 .page(params[:page])
                 .per(10)
  end
  
  def show
    @posts = @user.posts.published.recent.limit(5)
  end
  
  def create
    @user = User.new(user_params)
    
    if @user.save
      redirect_to @user, notice: 'User was successfully created.'
    else
      render :new
    end
  end
  
  def update
    if @user.update(user_params)
      redirect_to @user, notice: 'User was successfully updated.'
    else
      render :edit
    end
  end
  
  def destroy
    @user.destroy
    redirect_to users_url, notice: 'User was successfully deleted.'
  end
  
  private
  
  def set_user
    @user = User.find(params[:id])
  end
  
  def user_params
    params.require(:user).permit(:name, :email, :status)
  end
end

# API controller
class Api::V1::UsersController < ApiController
  before_action :set_user, only: [:show, :update, :destroy]
  
  def index
    @users = User.all
    render json: @users, each_serializer: UserSerializer
  end
  
  def show
    render json: @user, serializer: UserDetailSerializer
  end
  
  def create
    @user = User.new(user_params)
    
    if @user.save
      render json: @user, serializer: UserSerializer, status: :created
    else
      render json: { errors: @user.errors }, status: :unprocessable_entity
    end
  end
  
  private
  
  def set_user
    @user = User.find(params[:id])
  end
  
  def user_params
    params.require(:user).permit(:name, :email)
  end
end

# Background job
class UserDataProcessingJob < ApplicationJob
  queue_as :default
  
  def perform(user_id)
    user = User.find(user_id)
    
    # Execute heavy processing in background
    user.process_analytics_data
    user.update(processed_at: Time.current)
  end
end

# Job enqueue
UserDataProcessingJob.perform_later(user.id)

# Testing (RSpec)
RSpec.describe User, type: :model do
  describe 'validations' do
    it { should validate_presence_of(:name) }
    it { should validate_presence_of(:email) }
    it { should validate_uniqueness_of(:email) }
  end
  
  describe 'associations' do
    it { should have_many(:posts).dependent(:destroy) }
  end
  
  describe '#full_name' do
    let(:user) { build(:user, first_name: 'John', last_name: 'Doe') }
    
    it 'returns the full name' do
      expect(user.full_name).to eq('John Doe')
    end
  end
end

# Factory (FactoryBot)
FactoryBot.define do
  factory :user do
    name { Faker::Name.name }
    email { Faker::Internet.unique.email }
    status { 'active' }
    
    trait :with_posts do
      after(:create) do |user|
        create_list(:post, 3, user: user)
      end
    end
  end
  
  factory :post do
    title { Faker::Lorem.sentence(word_count: 4) }
    content { Faker::Lorem.paragraph(sentence_count: 5) }
    association :user
    published_at { 1.day.ago }
  end
end

# Seeds
# db/seeds.rb
User.create!([
  {
    name: 'Admin User',
    email: '[email protected]',
    status: 'active'
  },
  {
    name: 'Test User',
    email: '[email protected]',
    status: 'active'
  }
])

# Performance monitoring for production
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  
  # Slow query logging
  around_action :log_slow_queries
  
  private
  
  def log_slow_queries
    start_time = Time.current
    yield
    duration = Time.current - start_time
    
    if duration > 1.second
      Rails.logger.warn "Slow query detected: #{duration}s"
    end
  end
end