モデルの属性に自作のバリデーションを追加する

モデルの属性のバリデーションをしたいときに、バリデーションヘルパーの既存バリデーションだけでは足りないことがあると思います。

そのため、Rails ではバリデーションを自作することができます。

その一つの方法としてバリデーションメソッドがあります。

バリデーションメソッド

バリデーション用のメソッドを定義して、オブジェクトの保存時にそのメソッドによりバリデーションする方法です。

例えば、 name 属性を持つ Account モデルに対して、 name の prefix として hoge_ があるというバリデーションをしたい (?) 場合には、以下のようにします。

class Account < ActiveRecord::Base
  validate :prefix_hoge # 保存時に prefix_hoge によりバリデーションをする

  # バリデーションメソッド
  def prefix_hoge
    if name !~ /^hoge\_/ # バリデーションの条件
      errors.add(:name, " は hoge_ から始まるようにしましょう!") # エラーメッセージ
    end
  end
end

これにより、 prefix として hoge_ のない name を持つ Account のオブジェクトを保存しようとするとエラーになります。

# hoge_ あり
irb(main):001:0> Account.first.update(name: 'hoge_test')
  Account Load (0.3ms)  SELECT  `accounts`.* FROM `accounts`  ORDER BY `accounts`.`id` ASC LIMIT 1
   (0.2ms)  BEGIN
  SQL (0.3ms)  UPDATE `accounts` SET `name` = 'hoge_test', `updated_at` = '2016-05-13 14:59:13' WHERE `accounts`.`id` = 1
   (0.6ms)  COMMIT
=> true

# hoge_ なし
irb(main):002:0> Account.first.update(name: 'test')
  Account Load (0.3ms)  SELECT  `accounts`.* FROM `accounts`  ORDER BY `accounts`.`id` ASC LIMIT 1
   (0.1ms)  BEGIN
   (0.2ms)  ROLLBACK
=> false

ですが、 複数の属性に対してバリデーションヘルパーとバリデーションメソッドの両方を利用すると、以下のように同一の属性のバリデーションの記述が分散してしまうことがあります。

class Account < ActiveRecord::Base
  validates  :name,
    presence: true,
    length: { maximum: 10 }
    
  validates :email,
    presence: true

  validate :prefix_hoge

  validate :email_validation

バリデーションを自作する他の方法として、カスタムバリデータがあります。 こちらを使えば上記の点を防ぐことができます。

カスタムバリデータ

先ほどと同様のバリデーションを追加します。
カスタムバリデータは、 バリデーションを自作するためのクラスを継承したクラスを作ります。
クラスには ActiveModel::Validator と ActiveModel::EachValidator の二種類があるのですが、後者の方が使いやすいと思うのでそちらについてのみ書きます。

クラスをパスの通っている場所に作成します。

# lib/validators/prefix_hoge_validator.rb

class PrefixHogeValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value) # バリデーションメソッド
    if value !~ /^hoge\_/
      record.errors.add(attribute, " は hoge_ から始まるようにしましょう!")
    end
  end
end

バリデーションメソッドは必ず validate_each とします。

validate_each の引数の record に保存対象のオブジェクトが、 attribute にバリデーション対象の属性が、 value に保存する値 が渡ってきます。

これを Account モデルで利用する際には、バリデーションヘルパーのように記述することができます。

class Account < ActiveRecord::Base
  validates :name,
    prefix_hoge: true

これで先ほどと同様のバリデーションをすることができます。

カスタムバリデータであれば、複数の属性に対してバリデーションヘルパーとバリデーションメソッドの両方を利用したとしても、以下のように対象の属性の記述が一箇所にまとまり見やすくなります。

class Account < ActiveRecord::Base
  validates  :name,
    presence: true,
    length: { maximum: 10 },
    prefix_hoge: true    

  validates :email,
    presence: true
    email_validation: true

とはいえ、毎回カスタムバリデータを使うのではなく、それぞれのメリットデメリットを見極めて使うと良さそうです。