読者です 読者をやめる 読者になる 読者になる

【Rails】カスタムバリデータの使い方

スポンサーリンク

Rails のバリデーションは標準でも様々な検証が可能ですが、バリデータクラスを自作することで責務も分割されテストが容易になります。

EachValidatorクラス

1つの属性を検証するための新しいバリデータを追加したい場合はActiveModel::EachValidatorを継承したクラスを定義します。 組み込みのpresenceuniquenessなどのバリデータもEachValidatorのサブクラスとして実装されています。

バリデータクラス名は<検証名>Validatorの形式で命名してvalidate_eachメソッドを実装してください。 validate_eachメソッドは引数として以下の3つを受け取ります。

  • 検証対象のモデルインスタンス ( record )
  • 検証対象の属性名 ( attribute )
  • 検証対象の属性値 ( value )

例としてURLのフォーマットを検証するバリデータを作成しました。
ファイル保存場所はapp/validators/url_format_validator.rbです。

class UrlFormatValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    if value.present? && value !~ /\A#{URI::regexp(%w(http https))}\z/
      record.errors[attribute] << (options[:message] || I18n.t('validators.format'))
    end
  end
end

バリデータの使用方法

上記のEachValidatorを継承したクラスが定義されるとvalidatesメソッドがそのクラス名に基づいたオプション値を受け取れるようになります。バリデータクラスを作成したのがappディレクトリ以下であればサーバの再起動だけで使用できるようになります。autoload_pathsに作成したapp/validatorsディレクトリも含まれていますね。

$ ./bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths'
Running via Spring preloader in process 34261
...
/Users/tasukujp/Documents/bookshelf/app/mailers
/Users/tasukujp/Documents/bookshelf/app/models
/Users/tasukujp/Documents/bookshelf/app/validators
...

 autoload_paths | Rails ガイド

クラス名からValidatorを取り除いた、アンダースコア形式で表したものをvalidatesメソッドに記述します。UrlFormatValidatorであればurl_formatです。他にパラメータが無いため true のみ引き渡してください。

validates :url, url_format: true

パラメータ付きのバリデータ

パラメータを受け取るバリデータを作成することもできます。true を引き渡してた箇所にハッシュ形式でパラメータを渡して下さい。

validates :url, url_format: { ssl_only: true }

パラメータ情報にはoptions[パラメータ名]でアクセスできます。以下のようにssl_onlyパラメータが true であれば SSL サイトのみ許可するバリデータに変更しました。

class UrlFormatValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    protocol = options[:ssl_only] ? %w(https) : %w(http https)
    if value.present? && value !~ /\A#{URI::regexp(protocol)}\z/
        record.errors[attribute] << (options[:message] || I18n.t('validators.format'))
    end
  end
end

options[:message]も実はパラメータ情報でしたのでmessage: 'はSSLサイトのみです。'などを渡せばメッセージを自由に設定することが可能です。

復数の属性を使用

基本は1つの属性を検証するためのものですが、別の属性を参照することもできます。

# パラメータ情報で属性名を渡す
validates :url, url_format: { attr: 'server' }
 ↓
record.attributes[options[:attr]]

# 直接プロパティを参照
record.server

Validatorクラス

1つの属性値だけではなく複雑な検証ルールが必要になった場合にはActiveModel::Validatorを継承したクラスを定義します。

EachValidator と違いクラス名は特に決まりはありませんが<検証名>Validatorの形式がいいでしょう。validateメソッドを実装する必要があります。引数は1つで、検証対象のモデルインスタンス ( record ) のみです。

受け取ったモデルインスタンスに対してバリデーションを実行するため、比較的自由な検証をすることが可能です。

class NameStartsWidthValidator < ActiveModel::Validator
  def validate(record)
    unless record.name.starts_with? 'X'
      record.errors[:name] << (options[:message] || '名前はXで始まる必要があります')
    end
  end
end

バリデータの使用方法

このバリデータはvalidates_withメソッドを使用して呼び出し、引数はバリデータクラスそのものを渡します。

validates_with NameStartsWidthValidator

EachValidator と同じく引数に、ハッシュを利用してパラメータ情報を渡せます。渡されたオプションはoptionsというメソッドを呼ぶことで参照可能です。

validates_with NameValidator, { message: 'テストバリデータ' }

バリデータクラスは定義メソッドが呼び出された時点でインスタンスが生成され、モデルクラスに登録されます。実際にバリデーションを行う際には、同一のバリデーターのインスタンスが継続的に利用されます。そのため、バリデータクラスには状態を持たせないように注意してください。

パラメータ情報もインスタンス化された時に登録されるので、あるモデルクラスに対しては同じ値がずっと継続的に利用されます。

 Active Record バリデーション | Rails ガイド