🔖

[Rails]カスタムバリデータを実装する

2021/02/08に公開

複数間のモデルで同じ条件で検証したい属性がある時、カスタムバリデータを作成することで
同じロジックを各モデルに実装する必要がなくなります。
カスタムバリデータには2種類ありますので、目的に合わせてどちらを使うか選択してください。

validatorクラスを自作する

まず、 validators ディレクトリを作ってそこに hoge_validator.rb を作成します。
Railsは、自動で hoge_validator を読み取ってくれます。

個別の属性を検証する場合

1つの属性を検証したい時はEachValidatorが役に立ちます。

class DateValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.blank?

    record.errors.add(attribute, '日付は今日より未来を選択してください') if   value.to_date < Time.zone.today
  end
end

EachValidatorを利用するには、ActiveModel::EachValidatorを継承したクラスを作成します。そしてこのバリデータクラスは必ず validate_each メソッドを実装しなければいけません。validate_eachメソッドは次の3つの引数を受け取ります。

  1. record(モデルインスタンス)
  2. attribute(検証する属性)
  3. value(検証する属性値)

このカスタムバリデータは次の様にvalidatesメソッドを使って呼び出すことが出来ます。

class User < ApplicationRecord
  validates :date, date: true
end

複雑な条件下で検証する必要がある場合

ActiveModel::Validator を継承したクラスを実装します。このクラスは必ず validate メソッドを実装する必要があります。validateメソッドは record(モデルのインスタンス) を引数に受け取ります。

例として、Articleモデルには公開日(start_date)と公開終了日(end_date)があり、公開終了日は公開日以降でなければいけないことを検証するときは次の様になります。

class StartEndDateValidator < ActiveModel::Validator
  def validate(record)
    start_date = options[:start_date]
    end_date = options[:end_date]
    return unless start_date.present? && end_date.present? && start_date > end_date

    record.errors.add(options[:end], :end_date_greater_than_start_date, start: record.class.human_attribute_name(options[:start]))
  end
end

このカスタムバリデータを使うには次の様に validate_with メソッドで呼び出します。

class ArticleForm
  include ActiveModel::Model
  include ActiveModel::Validations
  include ActiveModel::Attributes

  attribute :start_date, :date
  attribute :end_date, :date

  validate_with StartEndDateValidator, start: :start_date, end: :end_date, start_date: start_date, end_date: end_date 
end

validate_with の第二引数以降はoptionとしてバリデータクラスで使うことが出来ます。

Discussion