ActiveRecordとは?現場で必要な基礎知識
ActiveRecordは、Ruby on Railsのデータベース操作を担う重要なコンポーネントです。オブジェクト指向プログラミングとリレーショナルデータベースの橋渡しを行うORマッパー(Object-Relational Mapper)として機能し、データベース操作を直感的に行うことができます。
ActiveRecordが解決する3つの開発課題
- SQLの複雑な記述からの解放
- 従来のSQL文の代わりに、Rubyのメソッドチェーンで直感的にクエリを記述可能
- データベース固有のSQL文法の違いを吸収し、統一的なインターフェースを提供
# 従来のSQL
# SELECT * FROM users WHERE age >= 20 ORDER BY created_at DESC LIMIT 10;
# ActiveRecordを使用した場合
User.where('age >= ?', 20).order(created_at: :desc).limit(10)
- モデルとデータベースの一元管理
- テーブルスキーマの変更をマイグレーションファイルで管理
- モデルの関連付けを直感的に定義可能
class User < ApplicationRecord
# has_manyによる1対多の関連付け
has_many :posts
# バリデーションの定義
validates :email, presence: true, uniqueness: true
validates :age, numericality: { greater_than_or_equal_to: 0 }
end
- データの整合性維持の自動化
- バリデーションによるデータ検証の自動化
- トランザクション処理の簡略化
- 関連するレコードの整合性管理
ActiveRecordの動作の仕組みと重要な概念
1. モデルの継承とスキーマの自動認識
ActiveRecordはApplicationRecord(またはActiveRecord::Base)を継承することで、対応するテーブルのスキーマを自動的に認識します。
# app/models/product.rb class Product < ApplicationRecord # テーブル名は自動的に'products'と認識される # カラム情報も自動的に取得される end # モデルの使用例 product = Product.new( name: "Ruby本", price: 3000 ) product.save # INSERT文が自動生成される
2. 命名規則(Convention over Configuration)
ActiveRecordは以下の命名規則に従います:
| モデル(単数形) | テーブル(複数形) | 外部キー |
|---|---|---|
| User | users | user_id |
| Category | categories | category_id |
| Person | people | person_id |
3. コールバックとオブザーバー
モデルの保存や更新時に自動的に処理を実行できます:
class User < ApplicationRecord
# 保存前に実行
before_save :ensure_name_has_a_value
# 作成後に実行
after_create :send_welcome_email
private
def ensure_name_has_a_value
self.name = 'Anonymous' if name.blank?
end
def send_welcome_email
UserMailer.welcome(self).deliver_later
end
end
4. Dirty Object Tracking
モデルの属性変更を追跡する機能を提供します:
user = User.find(1)
user.name = "新しい名前"
user.changed? # => true
user.changes # => {"name"=>["古い名前", "新しい名前"]}
user.name_changed? # => true
user.name_was # => "古い名前"
このように、ActiveRecordは豊富な機能を提供しながら、直感的なAPIでデータベース操作を可能にします。次のセクションでは、これらの基本知識を活用した実践的なテクニックを紹介していきます。
ActiveRecordを使いこなすための実践テクニック
複雑な関連付けを効率的に扱う方法
1. ポリモーフィック関連付けの実装
複数のモデルに対して同じ関連付けを設定する場合に有効です:
# app/models/comment.rb class Comment < ApplicationRecord belongs_to :commentable, polymorphic: true end # app/models/post.rb class Post < ApplicationRecord has_many :comments, as: :commentable end # app/models/photo.rb class Photo < ApplicationRecord has_many :comments, as: :commentable end # 使用例 post = Post.first post.comments.create(content: "Great post!") photo = Photo.first photo.comments.create(content: "Nice photo!")
2. 入れ子関係の効率的な処理
accepts_nested_attributes_forを使用して、親子関係のある複数のモデルを一度に更新できます:
class Order < ApplicationRecord
has_many :order_items
accepts_nested_attributes_for :order_items, allow_destroy: true
# 特定の条件で子レコードを削除
accepts_nested_attributes_for :order_items,
reject_if: proc { |attributes| attributes['quantity'].blank? }
end
# フォームからの送信データを一括で保存
Order.create(
customer: "山田太郎",
order_items_attributes: [
{ product_id: 1, quantity: 2 },
{ product_id: 2, quantity: 1 }
]
)
バルクインサート・アップデートの実装方法
1. insert_allによる一括挿入
# 複数レコードを一度に挿入
User.insert_all([
{ name: '山田', email: 'yamada@example.com' },
{ name: '田中', email: 'tanaka@example.com' }
], returning: %w[ id name ])
# タイムスタンプを自動設定
User.insert_all(
[ { name: '佐藤' }, { name: '鈴木' } ],
record_timestamps: true
)
2. update_allによる一括更新
# 条件に合致する全レコードを更新
User.where(active: true)
.update_all(newsletter: true)
# 複数のカラムを同時に更新
Product.where('price > ?', 1000)
.update_all('price = price * 0.9, updated_at = ?', Time.current)
トランザクション処理の正しい使い方
1. 基本的なトランザクション
ActiveRecord::Base.transaction do david.withdraw(100) mary.deposit(100) end
2. ネストしたトランザクション
User.transaction do
User.create!(name: '山田')
# ネストしたトランザクション
Account.transaction(requires_new: true) do
Account.create!(user_id: user.id, balance: 0)
rescue StandardError
# この例外はネストしたトランザクションのみをロールバック
raise ActiveRecord::Rollback
end
# 外側のトランザクションは継続
end
3. 条件付きトランザクション
def transfer_money(from_account, to_account, amount)
Account.transaction(isolation: :serializable) do
# 残高チェック
raise InsufficientBalanceError unless from_account.sufficient_balance?(amount)
from_account.withdraw(amount)
to_account.deposit(amount)
# トランザクションの完了を保証
raise ActiveRecord::Rollback unless from_account.save && to_account.save
end
rescue ActiveRecord::RecordInvalid
# バリデーションエラーの処理
false
end
重要な実装のポイント
- トランザクションの分離レベル
:read_uncommitted:read_committed:repeatable_read:serializable
# 分離レベルの指定 Post.transaction(isolation: :serializable) do # 厳密な直列化が必要な処理 end
- デッドロック対策
retry_count = 0
begin
Post.transaction do
# デッドロックの可能性がある処理
end
rescue ActiveRecord::Deadlocked
retry_count += 1
raise if retry_count > 3
sleep(0.1 * retry_count)
retry
end
- コールバックとの連携
class Order < ApplicationRecord
after_commit :send_confirmation_email, on: :create
after_rollback :notify_admin_of_failure
private
def send_confirmation_email
OrderMailer.confirmation(self).deliver_later
end
def notify_admin_of_failure
AdminNotifier.failure_alert(self).deliver_now
end
end
これらのテクニックを適切に組み合わせることで、効率的で信頼性の高いデータベース操作を実現できます。次のセクションでは、これらの操作をさらに最適化する方法について説明します。
パフォーマンスを最適化するActiveRecordの使い方
N+1問題を回避するincludesの効果的な使用法
N+1問題とは
N+1問題は、関連付けられたレコードを取得する際に発生する余分なクエリ発行の問題です。以下の例で説明します:
# N+1問題が発生するコード posts = Post.all posts.each do |post| puts post.user.name # 各投稿に対してユーザー情報を取得するクエリが発行される end # 実行されるSQL # SELECT * FROM posts # SELECT * FROM users WHERE id = 1 # SELECT * FROM users WHERE id = 2 # SELECT * FROM users WHERE id = 3 # ...
includes/preloadによる解決
# N+1問題を解決するコード posts = Post.includes(:user) posts.each do |post| puts post.user.name # 追加のクエリは発行されない end # 実行されるSQL # SELECT * FROM posts # SELECT * FROM users WHERE id IN (1, 2, 3, ...)
複雑な関連付けでの最適化
# 複数の関連付けを同時に最適化
posts = Post.includes(:user, comments: :user)
# 条件付きの関連付け
posts = Post.includes(:comments)
.where(comments: { status: 'approved' })
.references(:comments)
# ネストした関連付け
posts = Post.includes(comments: [{ user: :profile }, :likes])
インデックスを活用したクエリの高速化手法
1. 適切なインデックス設計
# マイグレーションでのインデックス設定
class AddIndexesToPosts < ActiveRecord::Migration[7.0]
def change
# 単一カラムインデックス
add_index :posts, :published_at
# 複合インデックス
add_index :posts, [:user_id, :published_at]
# ユニークインデックス
add_index :posts, :slug, unique: true
# 部分インデックス
add_index :posts, :published_at,
where: "status = 'published'"
end
end
2. クエリ最適化の実践例
# インデックスを活用するクエリ
class Post < ApplicationRecord
# 頻繁に使用される検索条件にスコープを定義
scope :recent_published, -> {
where(status: 'published')
.order(published_at: :desc)
.limit(10)
}
# 複合インデックスを活用する検索
scope :by_author_and_date, ->(author_id, date) {
where(author_id: author_id)
.where('published_at >= ?', date)
}
end
キャッシュ戦略でレスポンスを改善する方法
1. モデルキャッシュ
# キャッシュキーの設定
class Post < ApplicationRecord
include ActiveModel::Caching
# タッチオプションで関連モデルのキャッシュも更新
belongs_to :user, touch: true
# キャッシュキーのカスタマイズ
def cache_key
"#{super}-#{Digest::MD5.hexdigest(content)}"
end
end
2. クエリキャッシュ
# find_byの結果をキャッシュ
def find_post_by_slug(slug)
Rails.cache.fetch("post/#{slug}", expires_in: 12.hours) do
Post.find_by(slug: slug)
end
end
# 複雑なクエリ結果のキャッシュ
def trending_posts
Rails.cache.fetch('trending_posts', expires_in: 1.hour) do
Post.joins(:views)
.group('posts.id')
.order('COUNT(views.id) DESC')
.limit(10)
.to_a
end
end
3. ロウレベルキャッシュ
class Post < ApplicationRecord
# カウンターキャッシュ
belongs_to :category, counter_cache: true
# 算出値のキャッシュ
def cached_comments_count
Rails.cache.fetch([self, 'comments_count']) do
comments.count
end
end
end
4. フラグメントキャッシュの最適化
# ビューでのキャッシュ
<% cache_if @post.cacheable?, @post do %>
<article class="post">
<%= render @post.content %>
<% cache @post.comments do %>
<%= render @post.comments %>
<% end %>
</article>
<% end %>
# ロシアンドールキャッシュ
<% cache ['v1', @post] do %>
<%= render @post %>
<% cache ['v1', @post, :comments] do %>
<%= render @post.comments %>
<% end %>
<% end %>
パフォーマンス監視とチューニングのポイント
- クエリログの監視
# development.rbでの設定 config.after_initialize do Bullet.enable = true Bullet.alert = true Bullet.rails_logger = true end
- パフォーマンス計測
# 実行時間の計測
ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
if event.duration > 100
Rails.logger.warn("Slow query detected: #{event.payload[:sql]}")
end
end
これらの最適化テクニックを適切に組み合わせることで、アプリケーションのパフォーマンスを大きく改善できます。次のセクションでは、パフォーマンスに影響を与える可能性のあるアンチパターンについて説明します。
ActiveRecordのアンチパターンと対処法
メモリ使用量が増大する危険な実装パターン
1. 大量のレコードの一括読み込み
# 危険なパターン all_users = User.all.to_a # 全レコードをメモリに展開 # 推奨される実装 User.find_each do |user| # 一定数ずつ処理 end # バッチサイズの指定 User.find_each(batch_size: 100) do |user| # 処理 end
2. 不必要なカラムの読み込み
# メモリを無駄に使用 users = User.all.select(:id, :name, :email, :created_at, :updated_at) # 必要なカラムのみ指定 users = User.select(:id, :name) # 必要最小限のカラム # 特定のカラムを除外 users = User.select(User.attribute_names - ['huge_data_column'])
3. 巨大な配列の生成
# メモリ使用量が増大する実装
def self.all_user_names
pluck(:name) # 全ユーザー名を配列として保持
# メモリ効率の良い実装
def self.process_user_names
pluck(:name).find_each do |name|
yield name
end
end
デッドロックを引き起こす典型的なミス
1. 不適切なロック順序
# デッドロックの危険があるコード
def transfer_money(from_account, to_account, amount)
from_account.with_lock do
to_account.with_lock do
# 処理
end
end
end
# 改善された実装(IDでロック順序を固定)
def transfer_money(from_account, to_account, amount)
first, second = [from_account, to_account].sort_by(&:id)
first.with_lock do
second.with_lock do
# 処理
end
end
end
2. 長時間のトランザクション
# 問題のある実装
def process_large_data
Transaction.transaction do
huge_data.each do |data|
# 時間のかかる処理
end
end
end
# 改善された実装
def process_large_data
huge_data.each_slice(100) do |batch|
Transaction.transaction do
batch.each do |data|
# 処理
end
end
end
end
3. 不適切なインデックス設計
# デッドロックを誘発する可能性がある更新
class AddIndexWithoutLockStrategy < ActiveRecord::Migration[7.0]
def change
add_index :large_table, :column_name # テーブルロックが発生
# 代わりに
add_index :large_table, :column_name, algorithm: :concurrently
end
end
アンチパターンを回避するためのベストプラクティス
- メモリ使用量の最適化
- 必要なカラムのみを選択
- バッチ処理の活用
- ストリーミング処理の利用
# ストリーミング処理の例
require 'csv'
CSV.open('users.csv', 'w') do |csv|
User.select(:id, :name).find_each do |user|
csv << [user.id, user.name]
end
end
- デッドロック対策
- トランザクションの範囲を最小限に
- ロック順序の一貫性維持
- 適切なタイムアウト設定
# タイムアウト設定の例 config.active_record.lock_timeout = 5.seconds config.active_record.statement_timeout = 10.seconds
- パフォーマンスモニタリング
# クエリ実行時間の監視
ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
if event.duration > 100
Rails.logger.warn("Slow query detected: #{event.payload[:sql]}")
Rails.logger.warn("Duration: #{event.duration}ms")
end
end
これらのアンチパターンを理解し、適切な対処法を実装することで、より安定したアプリケーションの運用が可能になります。次のセクションでは、さらに高度なActiveRecordのテクニックについて説明します。
実務で使える高度なActiveRecordテクニック
ポリモーフィック関連付けの活用シーン
1. 通知システムの実装
# app/models/notification.rb
class Notification < ApplicationRecord
belongs_to :notifiable, polymorphic: true
belongs_to :recipient, class_name: 'User'
scope :unread, -> { where(read_at: nil) }
scope :recent, -> { order(created_at: :desc) }
end
# app/models/comment.rb
class Comment < ApplicationRecord
has_many :notifications, as: :notifiable, dependent: :destroy
after_create :create_notification
private
def create_notification
Notification.create(
notifiable: self,
recipient: post.user,
action: 'commented',
message: "#{user.name}があなたの投稿にコメントしました"
)
end
end
# app/models/like.rb
class Like < ApplicationRecord
has_many :notifications, as: :notifiable, dependent: :destroy
after_create :create_notification
end
2. 添付ファイル管理システム
# app/models/attachment.rb
class Attachment < ApplicationRecord
belongs_to :attachable, polymorphic: true
has_one_attached :file
validates :file, presence: true
validates :file, content_type: {
in: %w[image/jpeg image/png application/pdf],
message: '許可されていないファイル形式です'
}
end
# 使用例
class Report < ApplicationRecord
has_many :attachments, as: :attachable, dependent: :destroy
accepts_nested_attributes_for :attachments, allow_destroy: true
end
サービスクラスとの効果的な併用方法
1. 複雑な業務ロジックの分離
# app/services/order_processor.rb
class OrderProcessor
def initialize(order)
@order = order
end
def process
ApplicationRecord.transaction do
update_inventory
process_payment
send_notifications
end
rescue => e
handle_error(e)
false
end
private
def update_inventory
@order.order_items.each do |item|
item.product.with_lock do
raise InsufficientStock unless item.product.sufficient_stock?(item.quantity)
item.product.decrease_stock(item.quantity)
end
end
end
def process_payment
payment = Payment.create!(
order: @order,
amount: @order.total_amount
)
PaymentGateway.new(payment).process
end
end
# 使用例
class OrdersController < ApplicationController
def create
@order = Order.new(order_params)
if OrderProcessor.new(@order).process
redirect_to @order, notice: '注文が完了しました'
else
render :new
end
end
end
2. フォームオブジェクトとの連携
# app/forms/user_registration_form.rb
class UserRegistrationForm
include ActiveModel::Model
attr_accessor :email, :password, :terms_accepted
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :password, length: { minimum: 8 }
validates :terms_accepted, acceptance: true
def save
return false unless valid?
User.transaction do
user = User.create!(
email: email,
password: password
)
Profile.create!(user: user)
UserMailer.welcome(user).deliver_later
end
rescue ActiveRecord::RecordInvalid
false
end
end
テスト駆動開発におけるActiveRecordの扱い方
1. ファクトリを活用したテスト
# spec/factories/users.rb
FactoryBot.define do
factory :user do
sequence(:email) { |n| "user#{n}@example.com" }
password { "password123" }
# 関連付けを持つファクトリ
factory :user_with_posts do
transient do
posts_count { 5 }
end
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
end
end
end
# spec/models/user_spec.rb
RSpec.describe User, type: :model do
describe '#active_posts' do
let(:user) { create(:user_with_posts) }
before do
user.posts.first.update(status: 'archived')
end
it 'returns only active posts' do
expect(user.active_posts.count).to eq 4
end
end
end
2. モックとスタブの適切な使用
RSpec.describe OrderProcessor do
let(:order) { create(:order) }
let(:processor) { OrderProcessor.new(order) }
describe '#process' do
before do
allow(PaymentGateway).to receive(:new)
.and_return(double(process: true))
end
it 'processes the order successfully' do
expect(processor.process).to be true
end
context 'when stock is insufficient' do
before do
allow_any_instance_of(Product)
.to receive(:sufficient_stock?)
.and_return(false)
end
it 'fails to process the order' do
expect(processor.process).to be false
end
end
end
end
3. SharedExamplesの活用
RSpec.shared_examples 'softly deletable' do
let(:model) { described_class }
it 'can be soft deleted' do
instance = create(model.to_s.underscore)
expect {
instance.soft_delete
}.to change { instance.deleted_at }.from(nil)
end
it 'scopes active records' do
active = create(model.to_s.underscore)
deleted = create(model.to_s.underscore, deleted_at: Time.current)
expect(model.active).to include(active)
expect(model.active).not_to include(deleted)
end
end
RSpec.describe Post do
it_behaves_like 'softly deletable'
end
これらの高度なテクニックを適切に組み合わせることで、保守性が高く、テスト可能な実装を実現できます。次のセクションでは、ActiveRecordのモニタリングとデバッグ手法について説明します。
ActiveRecordのモニタリングとデバッグ手法
スロークエリを特定して改善する手順
1. ログの設定と解析
# config/environments/production.rb
config.active_record.verbose_query_logs = true
config.logger = ActiveSupport::Logger.new("log/production.log")
# スロークエリのログ設定
ActiveRecord::Base.logger = ActiveSupport::TaggedLogging.new(
ActiveSupport::Logger.new("log/slow_queries.log")
)
# クエリ実行時間の監視
ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
if event.duration > 1000 # 1秒以上かかるクエリ
Rails.logger.warn(<<~SQL)
Slow Query Detected:
Duration: #{event.duration}ms
SQL: #{event.payload[:sql]}
Location: #{event.payload[:name]}
SQL
end
end
2. パフォーマンス分析ツールの活用
# Gemfile group :development do gem 'rack-mini-profiler' gem 'bullet' gem 'rails-erd' # ERダイアグラム生成 end # config/environments/development.rb config.after_initialize do Bullet.enable = true Bullet.alert = true Bullet.rails_logger = true Bullet.add_footer = true Bullet.unused_eager_loading_enable = false end
本番環境での安全なデバッグ方法
1. 安全なデバッグ情報の収集
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
around_action :catch_and_log_exceptions
private
def catch_and_log_exceptions
yield
rescue => error
ErrorLogger.log(
error: error,
controller: controller_name,
action: action_name,
params: filtered_params,
user: current_user&.id
)
raise
end
def filtered_params
params.except('controller', 'action').to_unsafe_h
end
end
# lib/error_logger.rb
class ErrorLogger
def self.log(error:, **context)
Rails.logger.error(<<~ERROR)
Exception: #{error.class}
Message: #{error.message}
Context: #{context}
Backtrace:
#{error.backtrace&.first(10)&.join("\n")}
ERROR
end
end
2. デバッグモードの実装
# config/initializers/debug_mode.rb
module DebugMode
mattr_accessor :enabled
def self.enable!
self.enabled = true
ActiveRecord::Base.logger = Logger.new(STDOUT)
end
def self.disable!
self.enabled = false
ActiveRecord::Base.logger = Rails.logger
end
end
# 使用例
class ComplexQuery
def execute
if DebugMode.enabled
log_query_execution do
perform_query
end
else
perform_query
end
end
private
def log_query_execution
start_time = Time.current
result = yield
duration = Time.current - start_time
Rails.logger.info(<<~DEBUG)
Query Execution:
Duration: #{duration}s
Result Count: #{result.size}
DEBUG
result
end
end
モニタリングのベストプラクティス
1. メトリクスの収集
# config/initializers/metrics.rb
module ActiveRecordMetrics
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
after_create :track_creation
after_update :track_update
after_destroy :track_deletion
end
end
module ClassMethods
def track_metrics!
include ActiveRecordMetrics
end
end
private
def track_creation
StatsD.increment("#{self.class.name.underscore}.created")
end
def track_update
StatsD.increment("#{self.class.name.underscore}.updated")
end
def track_deletion
StatsD.increment("#{self.class.name.underscore}.deleted")
end
end
# モデルでの使用
class User < ApplicationRecord
track_metrics!
end
2. パフォーマンスモニタリング
module QueryMonitoring
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def monitor_queries!
around_query do |*, &block|
start_time = Time.current
result = block.call
duration = Time.current - start_time
if duration > threshold
alert_slow_query(duration)
end
result
end
end
private
def threshold
ENV.fetch('QUERY_THRESHOLD', 1.0).to_f
end
def alert_slow_query(duration)
SlackNotifier.notify(
channel: '#performance',
text: "Slow query detected (#{duration}s)"
)
end
end
end
これらのモニタリングとデバッグ手法を適切に実装することで、問題の早期発見と迅速な対応が可能になります。次のセクションでは、これまでの内容を踏まえたベストプラクティスについてまとめます。
ActiveRecordベストプラクティスまとめ
現場で使える7つの重要な設計指針
1. モデル設計の原則
- 単一責任の原則を守る
# 悪い例:モデルに多すぎる責任がある class User < ApplicationRecord # 認証、プロフィール、決済など多くの責任を持つ end # 良い例:責任を適切に分割 class User < ApplicationRecord has_one :profile has_one :payment_setting # 認証関連の処理 include Authenticatable # プロフィール関連の処理 delegate :full_name, :avatar_url, to: :profile end
2. クエリのパフォーマンス最適化
- 必要最小限のデータだけを取得する
# 基本的なスコープの定義
class Article < ApplicationRecord
scope :published_with_author, -> {
select('articles.id, articles.title, users.name as author_name')
.joins(:user)
.where(published: true)
}
# カスタムスコープビルダー
scope :with_required_columns, ->(columns) {
select(Array(columns))
}
end
3. バッチ処理とパフォーマンス
- メモリ使用量を考慮した実装
class BatchProcessor
def self.process_users
User.find_each(batch_size: 100) do |user|
UserStatisticsCalculator.new(user).calculate
rescue => e
Rails.logger.error("Failed to process user #{user.id}: #{e.message}")
next
end
end
end
4. トランザクション管理
- 適切な粒度でのトランザクション制御
class OrderProcessor
def process(order)
Order.transaction(requires_new: true) do
begin
update_inventory(order)
process_payment(order)
send_confirmation(order)
rescue => e
handle_error(e)
raise ActiveRecord::Rollback
end
end
end
end
5. テスタビリティの確保
- テスト容易性を考慮した設計
class Article < ApplicationRecord
# テスト可能な範囲でのバリデーション
validates :title, presence: true, length: { maximum: 100 }
validates :content, presence: true
# テスト可能なカスタムバリデーション
validate :publication_date_cannot_be_in_past
private
def publication_date_cannot_be_in_past
return unless publication_date.present?
if publication_date < Time.current
errors.add(:publication_date, "can't be in the past")
end
end
end
今すぐ始められる改善アクション
1. コードレビューチェックリスト
# モデルのチェックポイント
class ModelReviewer
def self.check_points
{
validation: '適切なバリデーションが設定されているか',
scope: 'スコープは適切に名前付けされ、効率的か',
callback: 'コールバックの副作用は考慮されているか',
relation: '関連付けは適切に設定されているか',
index: '必要なインデックスは設定されているか'
}
end
end
2. パフォーマンス改善ステップ
- N+1クエリの検出と改善
- Bulletgemの導入
- includes/preloadの適切な使用
- インデックス最適化
- 頻繁に使用されるクエリの分析
- 複合インデックスの検討
- キャッシュ戦略の実装
- モデルレベルのキャッシュ
- クエリキャッシュの活用
3. 継続的な改善のためのアクション
- コードレビューでの注意点チェック
- パフォーマンスモニタリングの習慣化
- 定期的なクエリログの分析
- チーム内でのベストプラクティス共有
これらの指針とアクションを実践することで、メンテナンス性が高く、パフォーマンスの良いRailsアプリケーションを構築・運用することができます。定期的にこれらの点をレビューし、必要に応じて改善を重ねていくことが重要です。