Active Record
Active RecordはRuby on RailsのORMです。Convention over Configurationの哲学により最小限の設定でActive Recordパターンを実装し、データベースマイグレーション、バリデーション、コールバック、アソシエーション機能を内蔵します。
GitHub概要
スター57,276
ウォッチ2,311
フォーク21,912
作成日:2008年4月11日
言語:Ruby
ライセンス:MIT License
トピックス
activejobactiverecordframeworkhtmlmvcrailsruby
スター履歴
データ取得日時: 2025/8/13 01:43
ライブラリ
Active Record
概要
Active RecordはRuby on RailsのORMです。Convention over Configurationの哲学により最小限の設定でActive Recordパターンを実装し、データベースマイグレーション、バリデーション、コールバック、アソシエーション機能を内蔵します。
詳細
Active Recordは、Martin Fowlerが定義したActive Recordパターンを忠実に実装したORMです。Railsの「設定より規約」の哲学を体現し、テーブル名やカラム名の規約に従うことで、最小限のコードでデータベース操作を実現します。モデルクラスにビジネスロジックとデータアクセス機能を統合し、直感的で表現力豊かなAPIを提供します。
主な特徴
- Convention over Configuration: 規約による設定の最小化
- 豊富なバリデーション: データ整合性を保つ検証機能
- アソシエーション: 直感的なリレーション定義
- コールバック: モデルライフサイクルでのフック
- マイグレーション: 安全なスキーマ変更管理
メリット・デメリット
メリット
- Ruby開発者にとって自然で読みやすいAPI
- 最小限の設定で高機能なデータベース操作を実現
- 豊富なアソシエーション機能で複雑なデータ関係も簡潔に表現
- Rails エコシステムとの完全な統合
- 長年の実績による安定性と信頼性
デメリット
- 大規模システムでのパフォーマンス課題
- Active Recordパターンによるビジネスロジックとデータアクセスの密結合
- Rails フレームワーク依存のため他のフレームワークでは使用困難
- 複雑なクエリの表現に制限がある場合がある
参考ページ
書き方の例
インストールと基本セットアップ
# Rails プロジェクト作成
rails new myapp
cd myapp
# データベース設定(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'] %>
基本的なCRUD操作(モデル定義、作成、読み取り、更新、削除)
# 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
# マイグレーション実行
# rails db:migrate
# 基本的なCRUD操作
# 作成
user = User.create(
name: '田中太郎',
email: '[email protected]'
)
# 読み取り
all_users = User.all
user_by_id = User.find(1)
user_by_email = User.find_by(email: '[email protected]')
active_users = User.active
# 更新
user = User.find(1)
user.update(name: '田中次郎')
# 一括更新
User.where(status: 'pending').update_all(status: 'active')
# 削除
user = User.find(1)
user.destroy
# 一括削除
User.where(status: 'inactive').destroy_all
高度なクエリとリレーションシップ
# 複雑な条件クエリ
active_users = User.where(status: 'active')
.where('created_at > ?', 30.days.ago)
.where('email LIKE ?', '%@company.com')
.order(created_at: :desc)
.limit(10)
# アソシエーションクエリ
users_with_posts = User.includes(:posts)
users_with_recent_posts = User.joins(:posts)
.where(posts: { created_at: 1.week.ago.. })
.distinct
# ネストしたアソシエーション
users_with_published_posts = User.joins(posts: :comments)
.where(posts: { published_at: ..Time.current })
.where(comments: { approved: true })
# 集約クエリ
user_stats = User.select('COUNT(*) as total_users, AVG(posts_count) as avg_posts')
.joins(:posts)
.group('users.id')
# カウンタクエリ
User.joins(:posts).group('users.id').count
Post.group(:user_id).count
# サブクエリ
active_users = User.where(id: Post.select(:user_id).where('created_at > ?', 1.month.ago))
# 複雑なスコープ
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
# スコープ使用例
active_productive_users = User.active
.with_recent_activity(7)
.by_post_count(3)
# 動的クエリ
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
マイグレーションとスキーマ管理
# マイグレーション作成
rails generate migration CreateUsers name:string email:string:index
rails generate migration AddStatusToUsers status:string
# マイグレーション実行
rails db:migrate
# ロールバック
rails db:rollback
rails db:rollback STEP=3
# マイグレーション状態確認
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
# カスタムマイグレーション
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
# データマイグレーション
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
パフォーマンス最適化と高度な機能
# トランザクション
ActiveRecord::Base.transaction do
user = User.create!(
name: '山田花子',
email: '[email protected]'
)
post = Post.create!(
title: '最初の投稿',
content: 'Active Recordを使った投稿です',
user: user,
published_at: Time.current
)
end
# バッチ操作
users_data = [
{ name: 'ユーザー1', email: '[email protected]' },
{ name: 'ユーザー2', email: '[email protected]' },
{ name: 'ユーザー3', email: '[email protected]' }
]
User.insert_all(users_data)
# バッチ更新
User.where(status: 'pending').update_all(
status: 'active',
activated_at: Time.current
)
# バッチ処理(find_each)
User.where(status: 'active').find_each(batch_size: 100) do |user|
user.send_newsletter
end
# includesでN+1問題を回避
posts = Post.includes(:user, :comments).limit(10)
posts.each do |post|
puts "#{post.title} by #{post.user.name} (#{post.comments.count} comments)"
end
# カウンタキャッシュ
class User < ApplicationRecord
has_many :posts, dependent: :destroy, counter_cache: true
end
class Post < ApplicationRecord
belongs_to :user, counter_cache: true
end
# カスタムバリデーション
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
# コールバック
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
# カスタムアトリビュート
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
フレームワーク統合と実用例
# コントローラー統合
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 コントローラー
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
# バックグラウンドジョブ
class UserDataProcessingJob < ApplicationJob
queue_as :default
def perform(user_id)
user = User.find(user_id)
# 重い処理をバックグラウンドで実行
user.process_analytics_data
user.update(processed_at: Time.current)
end
end
# ジョブエンキュー
UserDataProcessingJob.perform_later(user.id)
# テスト(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
# ファクトリー(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
# シード
# db/seeds.rb
User.create!([
{
name: 'Admin User',
email: '[email protected]',
status: 'active'
},
{
name: 'Test User',
email: '[email protected]',
status: 'active'
}
])
# 本番環境でのパフォーマンス監視
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
# スロークエリログ
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