😸
Rails validation contextの活用事例
1. 結論(この記事で得られること)
この記事では、Railsの「validation context」を実務でどう使いこなすかを具体的に解説します。
得られるもの:
- 「同一モデルで画面ごとにバリデーションを変えたい」という要件への実装パターン
- contextを使うべき場面・使うべきでない場面の判断基準
- テストコードを含めた実装例と、運用時の失敗パターン
- AIツールを使った validation 設計の最速チェック方法
実は昔、私も「admin画面では必須だけどユーザー画面では任意」という要件で、「if」文まみれのバリデーションを書いて後悔した経験があります。contextを知っていればもっとシンプルに書けたんですよね。
2. 前提(環境・読者層)
対象読者:
- Rails 5.x 〜 7.x で開発している方
- モデルのバリデーションが複雑化してきて困っている方
- 管理画面とユーザー画面で異なるバリデーションを実装したい方
検証環境:
Ruby: 3.2.2
Rails: 7.1.2
ただし、validation contextは Rails 3 からある機能なので、古めのプロジェクトでも使えます。
3. Before:よくあるつまずきポイント
3-1. 条件分岐地獄のバリデーション
こういうコード、見覚えありませんか?
class User < ApplicationRecord
validates :name, presence: true
validates :email, presence: true
# admin画面でのみ必須
validates :department_id, presence: true, if: :admin_context?
validates :employee_code, presence: true, if: :admin_context?
# ユーザー登録時のみ必須
validates :password, presence: true, if: :user_registration?
validates :terms_accepted, acceptance: true, if: :user_registration?
attr_accessor :context_type
def admin_context?
context_type == 'admin'
end
def user_registration?
context_type == 'registration'
end
end
これの何が問題か:
① インスタンス変数の管理が煩雑 → 「context_type」の設定漏れでバグる
② メソッドが増え続ける → 「admin_context?」, 「user_registration?」…
③ テストが書きづらい → 条件の組み合わせ爆発
3-2. 別モデルに切り出す誘惑
「だったら 「AdminUser」 と 「User」 でモデル分けるか…」という方向に行きがちですが、これも危険です。
# STIで分離
class User < ApplicationRecord
end
class AdminUser < User
validates :department_id, presence: true
end
class NormalUser < User
validates :terms_accepted, acceptance: true
end
問題点:
- DBテーブル設計が複雑化(STI の type カラム管理)
- ユーザーの種別変更が困難(一般→管理者への昇格など)
- 本質的に「同じユーザー」なのに別クラスで扱う違和感
4. After:基本的な解決パターン
4-1. validation context の基本
実はRailsにはcontextという仕組みが最初から用意されています。
class User < ApplicationRecord
# 常に必須
validates :name, presence: true
validates :email, presence: true, uniqueness: true
# adminコンテキストでのみ必須
validates :department_id, presence: true, on: :admin
validates :employee_code, presence: true,
format: { with: /\A[A-Z]{2}\d{4}\z/ },
on: :admin
# registrationコンテキストでのみ必須
validates :password, presence: true,
length: { minimum: 8 },
on: :registration
validates :terms_accepted, acceptance: true, on: :registration
end
使い方:
# 通常の保存(デフォルトのバリデーション+コンテキストなし)
user.save
# adminコンテキストで保存
user.save(context: :admin)
# registrationコンテキストで検証
user.valid?(:registration)
4-2. 複数コンテキストの指定
配列で複数指定も可能です。
class Article < ApplicationRecord
# 通常保存とpublishの両方で必須
validates :title, presence: true
validates :body, presence: true
# publish時のみ
validates :published_at, presence: true, on: :publish
validates :category_id, presence: true, on: :publish
# 下書き保存時は緩め(タイトルだけ)
validates :title, presence: true, on: :draft
end
コントローラーでの使い分け:
class ArticlesController < ApplicationController
def create
@article = Article.new(article_params)
context = params[:commit] == '公開' ? :publish : :draft
if @article.save(context: context)
redirect_to @article
else
render :new
end
end
end
4-3. いつ使うべきか・使わないべきか
使うべき場面:
- 管理画面 vs ユーザー画面で要件が異なる
- 下書き保存 vs 本番公開で厳密さが違う
- ステップ入力(ウィザード形式)で段階的に検証
使わないべき場面:
- 単なる条件分岐(「if: -> { status == 'active' }」で済むなら不要)
- ビジネスロジックの分離(Formオブジェクト等の検討を)
- 永続化しないフォーム(ActiveModel::Model の検討を)
私の経験則ですが、「保存するタイミング・画面によって要求が変わる」ならcontext、「データの状態によって要求が変わる」なら条件分岐という使い分けが良いです。
続きはnoteで
この記事の実装編・詳細解説はnoteで公開しています。
実際のコード例や、実務で遭遇するハマりポイントなど、より踏み込んだ内容を書いています。
Discussion