Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Railsの技: Active Recordバリデーションをコンテキストに応じて実行する(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Railsの技: Active Recordバリデーションをコンテキストに応じて実行する(翻訳)

データベースモデルで特定のバリデーションをスキップしたい場合があります。マルチ画面で構成されるウィザードを作る場合や、管理者がデータを自由に変更したい場合などが考えられます。

こういうときは特定のフォームでバリデーションをスキップしたくなるかもしれませんが、もっと良い方法があります。

Railsでは、レコードの保存やバリデーションの際にcontextを渡せます。contexton:オプションを組み合わせれば、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_rangeapplication_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のバリデーションでコンテキストを指定する場合、データベースレベルのバリデーションが行えなくなるというデメリットがあります。レコードの一部が無効でも永続化できることや、ルールに条件を設定できるのはコンテキストの強力な機能ですが、代償を伴うことになります。

バリデーションをデータベースレベルの制約レベルからアプリケーションに移すことについては慎重にお考えください。

参考資料

関連記事


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。