Railsの技: Active Recordバリデーションをコンテキストに応じて実行する(翻訳)
データベースモデルで特定のバリデーションをスキップしたい場合があります。マルチ画面で構成されるウィザードを作る場合や、管理者がデータを自由に変更したい場合などが考えられます。
こういうときは特定のフォームでバリデーションをスキップしたくなるかもしれませんが、もっと良い方法があります。
Railsでは、レコードの保存やバリデーションの際にcontext
を渡せます。context
とon:
オプションを組み合わせれば、ActiveRecordの特定のバリデーションだけを実行できるようになります。
利用法
たとえば、求人情報サイトでマルチステップのワークフローを構築したいとします。求人情報を作成してデータを入力している間は、求人情報を公開するまで以下のように項目の一部をバリデーションしないようにできます。
class Listing < ApplicationRecord
belongs_to :company
belongs_to :user
has_rich_text :requirements
validates :title, presence: true, length: { maximum: 50 }
validates :salary_range, presence: true, on: :publish
validates :application_instructions, presence: true, on: :publish
def publish!
self.published_at = Time.current
save(context: :publish)
end
end
この場合、title
はこのレコードの作成および編集時には常に省略不可(最大50文字)ですが、バリデーションのコンテキストに:publish
を渡した場合にのみsalary_range
とapplication_instructions
をバリデーションします。
このワークフローはコントローラのアクションで以下のように実装できます。
class ListingsController < ApplicationController
def create
@listing = Listing.new(listing_params)
if @listing.save
redirect_to @listing, notice: "Listing created"
else
render :new, status: :unprocessable_entity
end
end
def publish
@listing = Listing.find(params[:id])
if @listing.publish!
redirect_to @listing, notice: "Listing published"
else
render :edit, status: :unprocessable_entity
end
end
end
また、変更を行うユーザーに応じて異なるバリデーションを加えることもできます(管理者が友人に特別な短いユーザ名を与えることを許可したい場合など)。
以下では、ユーザー名は6文字以上を必須とするルールを:create
コンテキストに設定しています(Railsはレコード作成時にデフォルトでこの設定を含めます)。次に、:admin
コンテキストにユーザー名を3文字以上とするルールを追加します。
class Account < ApplicationRecord
validates :username, length: { minimum: 6 }, on: :create
validates :username, length: { minimum: 3 }, on: :admin
end
Account.new(username: "swanson").valid? # => true
Account.new(username: "swanson").valid?(:admin) # => true
Account.new(username: "mds").valid? # => false
Account.new(username: "mds").valid?(:admin) # => true
Account.new(username: "a").valid? # => false
Account.new(username: "a").valid?(:admin) # => false
Railsのバリデーションでコンテキストを指定する場合、データベースレベルのバリデーションが行えなくなるというデメリットがあります。レコードの一部が無効でも永続化できることや、ルールに条件を設定できるのはコンテキストの強力な機能ですが、代償を伴うことになります。
バリデーションをデータベースレベルの制約レベルからアプリケーションに移すことについては慎重にお考えください。
参考資料
-
Railsガイド: Active Record バリデーション
-
Rails APIドキュメント: ActiveRecord::Validations#valid?
概要
原著者の許諾を得て翻訳・公開いたします。