Rails のバリデーションは標準でも様々な検証が可能ですが、バリデータクラスを自作することで責務も分割されテストが容易になります。
EachValidatorクラス
1つの属性を検証するための新しいバリデータを追加したい場合はActiveModel::EachValidator
を継承したクラスを定義します。
組み込みのpresence
やuniqueness
などのバリデータも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 ...
クラス名から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 ガイド