ActiveRecord
ActiveRecord is "the core ORM (Object-Relational Mapping) library of Ruby on Rails" developed as the most essential database manipulation library in the Ruby ecosystem. Designed based on Martin Fowler's "Active Record" pattern, it automatically maps database tables to Ruby classes. Through an intuitive and readable API, it enables complex database operations without writing SQL statements, establishing itself as an indispensable tool for Rails developers.
GitHub Overview
Topics
Star History
Library
ActiveRecord
Overview
ActiveRecord is "the core ORM (Object-Relational Mapping) library of Ruby on Rails" developed as the most essential database manipulation library in the Ruby ecosystem. Designed based on Martin Fowler's "Active Record" pattern, it automatically maps database tables to Ruby classes. Through an intuitive and readable API, it enables complex database operations without writing SQL statements, establishing itself as an indispensable tool for Rails developers.
Details
ActiveRecord 2025 edition serves as the foundation of the Ruby on Rails framework with over 15 years of proven track record, providing the standard ORM solution for modern Ruby application development. Following the philosophy of Convention over Configuration, it offers powerful integration with relational databases with minimal setup. Rich features including migrations, validations, associations, scopes, and callbacks streamline enterprise-level database-driven application development. Supporting major database engines like MySQL, PostgreSQL, SQLite, and SQL Server, it provides a consistent development experience from development to production environments.
Key Features
- Automatic Mapping: Automatic correspondence between table names and class names with automatic schema detection
- Rich Associations: Intuitive relationship definitions like belongs_to, has_many, has_one, etc.
- Smart Query API: Readable query construction with where, joins, includes, etc.
- Migrations: Version control and automatic application of database schema changes
- Validations: Model-level data integrity checking functionality
- Transactions: Database transactions with automatic rollback functionality
Pros and Cons
Pros
- Overwhelming adoption rate in Rails ecosystem with abundant documentation and learning resources
- Configuration-less development and high productivity through Convention over Configuration
- Intuitive expression of complex data relationships through rich association features
- Simplified schema synchronization in team development through migration functionality
- Construction of reusable query logic through scope functionality
- Ensuring robust data integrity through validation functionality
Cons
- Increased memory usage and performance constraints when processing large amounts of data
- Limitations in expressing complex SQL queries requiring raw SQL in some cases
- Prone to N+1 query problems requiring proper eager loading
- Difficulty integrating with existing databases that don't follow table naming conventions
- Deteriorated code maintainability due to oversized model classes
- Limitations in utilizing database-specific features in some cases
Reference Pages
Code Examples
Basic Setup
# Gemfile
gem 'rails', '~> 7.1.0'
# Database configuration (config/database.yml)
development:
adapter: postgresql
database: myapp_development
username: postgres
password: password
host: localhost
port: 5432
# Generate model file
rails generate model User name:string email:string age:integer
# Run migration
rails db:migrate
# Test in Rails console
rails console
Model Definition and Basic Operations
# app/models/user.rb
class User < ApplicationRecord
# Validations
validates :name, presence: true, length: { minimum: 2 }
validates :email, presence: true, uniqueness: true
validates :age, presence: true, numericality: { greater_than: 0 }
# Scopes
scope :adults, -> { where('age >= ?', 18) }
scope :by_name, ->(name) { where('name ILIKE ?', "%#{name}%") }
# Callbacks
before_save :normalize_name
after_create :send_welcome_email
private
def normalize_name
self.name = name.strip.titleize
end
def send_welcome_email
UserMailer.welcome(self).deliver_now
end
end
# Basic CRUD operations
# Create
user = User.new(name: "John Doe", email: "[email protected]", age: 30)
user.save!
# Or
user = User.create!(name: "Jane Smith", email: "[email protected]", age: 25)
# Read
user = User.find(1) # Find by ID
user = User.find_by(email: "[email protected]") # Find by condition
users = User.where(age: 20..30) # Range specification
users = User.adults # Using scope
# Update
user.update!(name: "John Smith")
User.where(age: 18).update_all(status: 'adult')
# Delete
user.destroy!
User.where('created_at < ?', 1.year.ago).delete_all
Advanced Query Operations
# Complex condition combinations
users = User.where(age: 20..30)
.where.not(email: nil)
.where("name ILIKE ?", "%John%")
.order(:created_at)
.limit(10)
# JOIN queries
users_with_posts = User.joins(:posts)
.where(posts: { published: true })
.distinct
# Using subqueries
active_users = User.where(
id: Post.where('created_at > ?', 1.month.ago)
.select(:user_id)
.distinct
)
# Aggregate functions
User.count
User.where(age: 20..30).average(:age)
User.group(:department).count
User.having('COUNT(*) > 5').group(:city).count
# Using SQL fragments
User.where("age > ? AND created_at > ?", 18, 1.year.ago)
User.where("EXTRACT(year FROM created_at) = ?", 2024)
# Dynamic query construction
def search_users(params)
query = User.all
query = query.where('name ILIKE ?', "%#{params[:name]}%") if params[:name].present?
query = query.where(age: params[:min_age]..params[:max_age]) if params[:min_age] && params[:max_age]
query = query.where(department: params[:department]) if params[:department].present?
query = query.order(params[:sort_by] || :created_at)
query
end
# Usage example
users = search_users({
name: "John",
min_age: 20,
max_age: 40,
department: "engineering",
sort_by: :name
})
Relationship Operations
# Association definitions
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :authored_posts, class_name: 'Post', foreign_key: 'author_id'
has_one :profile, dependent: :destroy
belongs_to :department
has_many :user_roles
has_many :roles, through: :user_roles
has_many :following_relationships, class_name: 'Follow', foreign_key: 'follower_id'
has_many :following, through: :following_relationships, source: :followed
has_many :follower_relationships, class_name: 'Follow', foreign_key: 'followed_id'
has_many :followers, through: :follower_relationships, source: :follower
end
class Post < ApplicationRecord
belongs_to :user
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
has_many :commenters, through: :comments, source: :user
validates :title, presence: true
validates :content, presence: true, length: { minimum: 10 }
scope :published, -> { where(published: true) }
scope :recent, -> { where('created_at > ?', 1.week.ago) }
end
class Profile < ApplicationRecord
belongs_to :user
validates :bio, length: { maximum: 500 }
end
class Department < ApplicationRecord
has_many :users
validates :name, presence: true, uniqueness: true
end
# Relationship operations
user = User.find(1)
# Creating related records
post = user.posts.create!(title: "Hello World", content: "My first post")
profile = user.create_profile!(bio: "Software Developer")
# Fetching related records (avoiding N+1 problem)
users_with_posts = User.includes(:posts).where(posts: { published: true })
users_with_profiles = User.includes(:profile, :department)
# Fetching nested relationships
users_with_comments = User.includes(posts: :comments)
# Conditional fetching of related records
users_with_recent_posts = User.joins(:posts)
.where(posts: { created_at: 1.week.ago.. })
.distinct
# Aggregating related records
users_with_post_counts = User.left_joins(:posts)
.group(:id)
.select('users.*, COUNT(posts.id) as posts_count')
# Many-to-many relationship operations
user = User.find(1)
role = Role.find_by(name: 'admin')
user.roles << role # Add association
user.roles.delete(role) # Remove association
user.role_ids = [1, 2, 3] # Bulk set associations
Practical Examples
# Migration example
class CreateBlogSchema < ActiveRecord::Migration[7.1]
def change
# Users table
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false
t.integer :age
t.text :bio
t.references :department, null: true, foreign_key: true
t.timestamps
end
add_index :users, :email, unique: true
add_index :users, :name
# Posts table
create_table :posts do |t|
t.string :title, null: false
t.text :content, null: false
t.boolean :published, default: false
t.references :user, null: false, foreign_key: true
t.references :author, null: true, foreign_key: { to_table: :users }
t.timestamps
end
add_index :posts, [:user_id, :published]
add_index :posts, :created_at
# Comments table
create_table :comments do |t|
t.text :content, null: false
t.references :post, null: false, foreign_key: true
t.references :user, null: false, foreign_key: true
t.timestamps
end
# Departments table
create_table :departments do |t|
t.string :name, null: false
t.text :description
t.timestamps
end
add_index :departments, :name, unique: true
end
end
# Controller usage example
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
def index
@users = User.includes(:department, :posts)
.order(:name)
.page(params[:page])
end
def show
@recent_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, status: :unprocessable_entity
end
end
def update
if @user.update(user_params)
redirect_to @user, notice: 'User was successfully updated.'
else
render :edit, status: :unprocessable_entity
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, :age, :bio, :department_id)
end
end
# Service object usage example
class UserStatsService
def self.call(user)
new(user).call
end
def initialize(user)
@user = user
end
def call
{
total_posts: user.posts.count,
published_posts: user.posts.published.count,
total_comments: user.comments.count,
followers_count: user.followers.count,
following_count: user.following.count,
average_post_length: average_post_length,
most_commented_post: most_commented_post
}
end
private
attr_reader :user
def average_post_length
user.posts.published.average('LENGTH(content)')&.round(2) || 0
end
def most_commented_post
user.posts
.joins(:comments)
.group('posts.id')
.order('COUNT(comments.id) DESC')
.limit(1)
.select('posts.*, COUNT(comments.id) as comments_count')
.first
end
end
# Usage example
user = User.find(1)
stats = UserStatsService.call(user)
puts "Total posts: #{stats[:total_posts]}"
puts "Published posts: #{stats[:published_posts]}"
puts "Average post length: #{stats[:average_post_length]} characters"