ActiveRecord
ActiveRecordは「Ruby on Railsの中核を成すORM(Object-Relational Mapping)ライブラリ」として開発された、Rubyエコシステムで最も重要なデータベース操作ライブラリです。Martin Fowlerの「Active Record」パターンに基づいて設計され、データベースのテーブルとRubyクラスを自動的にマッピング。直感的で読みやすいAPIを通じて、SQL文を書くことなく複雑なデータベース操作を実現し、Rails開発者にとって必須のツールとして確立されています。
GitHub概要
トピックス
スター履歴
ライブラリ
ActiveRecord
概要
ActiveRecordは「Ruby on Railsの中核を成すORM(Object-Relational Mapping)ライブラリ」として開発された、Rubyエコシステムで最も重要なデータベース操作ライブラリです。Martin Fowlerの「Active Record」パターンに基づいて設計され、データベースのテーブルとRubyクラスを自動的にマッピング。直感的で読みやすいAPIを通じて、SQL文を書くことなく複雑なデータベース操作を実現し、Rails開発者にとって必須のツールとして確立されています。
詳細
ActiveRecord 2025年版はRuby on Railsフレームワークの基盤として15年以上の実績を持ち、現代的なRubyアプリケーション開発における標準的なORM解決策です。Convention over Configuration(設定より規約)の哲学に従い、最小限の設定でリレーショナルデータベースとの強力な統合を提供。マイグレーション、バリデーション、アソシエーション、スコープ、コールバックなど豊富な機能により、企業レベルのデータベース駆動アプリケーション開発を効率化。MySQL、PostgreSQL、SQLite、SQL Serverなど主要なデータベースエンジンに対応し、開発から本番環境まで一貫した開発体験を提供します。
主な特徴
- 自動マッピング: テーブル名とクラス名の自動的な対応付けとスキーマの自動検出
- 豊富なアソシエーション: belongs_to、has_many、has_one等の直感的な関連定義
- スマートクエリAPI: where、joins、includes等の読みやすいクエリ構築
- マイグレーション: データベーススキーマの版数管理と自動適用
- バリデーション: モデルレベルでのデータ整合性チェック機能
- トランザクション: 自動ロールバック機能付きのデータベーストランザクション
メリット・デメリット
メリット
- Railsエコシステムでの圧倒的な普及率と豊富なドキュメント・学習リソース
- Convention over Configurationによる設定レスな開発と高い生産性
- 豊富なアソシエーション機能による複雑なデータ関係の直感的な表現
- マイグレーション機能によるチーム開発でのスキーマ同期の簡素化
- スコープ機能による再利用可能なクエリロジックの構築
- バリデーション機能による堅牢なデータ整合性の確保
デメリット
- 大量データ処理時のメモリ使用量増加とパフォーマンス制約
- 複雑なSQLクエリの表現に限界があり生SQLが必要な場合がある
- N+1クエリ問題が発生しやすく適切なeager loadingが必要
- テーブル命名規約に従わない既存データベースとの統合が困難
- 巨大なモデルクラスによるコード保守性の悪化
- データベース固有機能の利用が制限される場合がある
参考ページ
書き方の例
基本セットアップ
# Gemfile
gem 'rails', '~> 7.1.0'
# データベース設定(config/database.yml)
development:
adapter: postgresql
database: myapp_development
username: postgres
password: password
host: localhost
port: 5432
# モデルファイル生成
rails generate model User name:string email:string age:integer
# マイグレーション実行
rails db:migrate
# Railsコンソールでの動作確認
rails console
モデル定義と基本操作
# app/models/user.rb
class User < ApplicationRecord
# バリデーション
validates :name, presence: true, length: { minimum: 2 }
validates :email, presence: true, uniqueness: true
validates :age, presence: true, numericality: { greater_than: 0 }
# スコープ
scope :adults, -> { where('age >= ?', 18) }
scope :by_name, ->(name) { where('name ILIKE ?', "%#{name}%") }
# コールバック
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
# 基本的なCRUD操作
# 作成(Create)
user = User.new(name: "田中太郎", email: "[email protected]", age: 30)
user.save!
# または
user = User.create!(name: "佐藤花子", email: "[email protected]", age: 25)
# 読み取り(Read)
user = User.find(1) # IDで検索
user = User.find_by(email: "[email protected]") # 条件で検索
users = User.where(age: 20..30) # 範囲指定
users = User.adults # スコープ使用
# 更新(Update)
user.update!(name: "田中次郎")
User.where(age: 18).update_all(status: 'adult')
# 削除(Delete)
user.destroy!
User.where('created_at < ?', 1.year.ago).delete_all
高度なクエリ操作
# 複雑な条件の組み合わせ
users = User.where(age: 20..30)
.where.not(email: nil)
.where("name ILIKE ?", "%田中%")
.order(:created_at)
.limit(10)
# JOINクエリ
users_with_posts = User.joins(:posts)
.where(posts: { published: true })
.distinct
# サブクエリの使用
active_users = User.where(
id: Post.where('created_at > ?', 1.month.ago)
.select(:user_id)
.distinct
)
# 集約関数
User.count
User.where(age: 20..30).average(:age)
User.group(:department).count
User.having('COUNT(*) > 5').group(:city).count
# SQLフラグメントの使用
User.where("age > ? AND created_at > ?", 18, 1.year.ago)
User.where("EXTRACT(year FROM created_at) = ?", 2024)
# 動的クエリ構築
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
# 使用例
users = search_users({
name: "田中",
min_age: 20,
max_age: 40,
department: "engineering",
sort_by: :name
})
リレーション操作
# アソシエーション定義
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
# リレーションの操作
user = User.find(1)
# 関連レコードの作成
post = user.posts.create!(title: "Hello World", content: "My first post")
profile = user.create_profile!(bio: "Software Developer")
# 関連レコードの取得(N+1問題を避ける)
users_with_posts = User.includes(:posts).where(posts: { published: true })
users_with_profiles = User.includes(:profile, :department)
# ネストした関連の取得
users_with_comments = User.includes(posts: :comments)
# 関連レコードの条件付き取得
users_with_recent_posts = User.joins(:posts)
.where(posts: { created_at: 1.week.ago.. })
.distinct
# 関連レコードの集約
users_with_post_counts = User.left_joins(:posts)
.group(:id)
.select('users.*, COUNT(posts.id) as posts_count')
# 多対多関係の操作
user = User.find(1)
role = Role.find_by(name: 'admin')
user.roles << role # 関連付け追加
user.roles.delete(role) # 関連付け削除
user.role_ids = [1, 2, 3] # 関連付け一括設定
実用例
# マイグレーション例
class CreateBlogSchema < ActiveRecord::Migration[7.1]
def change
# ユーザーテーブル
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
# 投稿テーブル
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
# コメントテーブル
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
# 部署テーブル
create_table :departments do |t|
t.string :name, null: false
t.text :description
t.timestamps
end
add_index :departments, :name, unique: true
end
end
# コントローラでの使用例
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: 'ユーザーが作成されました。'
else
render :new, status: :unprocessable_entity
end
end
def update
if @user.update(user_params)
redirect_to @user, notice: 'ユーザーが更新されました。'
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@user.destroy
redirect_to users_url, notice: 'ユーザーが削除されました。'
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
# サービスオブジェクトでの使用例
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
# 使用例
user = User.find(1)
stats = UserStatsService.call(user)
puts "総投稿数: #{stats[:total_posts]}"
puts "公開投稿数: #{stats[:published_posts]}"
puts "平均投稿長: #{stats[:average_post_length]} 文字"