Railsにて、モデルのカスタムバリデーションメソッドの中で、標準のエラーメッセージを利用する

Railsでは、標準で用意されているモデルのバリデーションの他に、カスタムバリデーションメソッドを作成することで、独自のバリデーションを行えます。
6 カスタムバリデーションを実行する | Active Record バリデーション - Railsガイド

 
そんな中、「カスタムバリデーションメソッドは作るけど、そこで発生するバリデーションエラーは標準で用意されているものを使いたい」場合に、どのようにすればよいか悩んだため、メモを残します。

 
目次

 

環境

 

調査

カスタムバリデーションメソッドでは、 errors.add() メソッドを使うことでエラーメッセージを設定できます。
7.4 errors.add | Active Record バリデーション - Railsガイド

 
このメソッドの定義を見たところ、引数として

  • attribute
    • 属性名
  • type
    • 文字列 or シンボル or Proc
  • **options

がありました。
https://api.rubyonrails.org/v7.0/classes/ActiveModel/Errors.html#method-i-add

 
type にシンボルを渡した場合は、以下のように書かれていました。

If type is a symbol, it will be translated using the appropriate scope (see generate_message).

person.errors.add(:name, :blank)
person.errors.messages
# => {:name=>["can't be blank"]}

person.errors.add(:name, :too_long, { count: 25 })
person.errors.messages
# => ["is too long (maximum is 25 characters)"]

 
次に generate_message のドキュメントを読むと、定義されているシンボルを渡せば良さそうに見えました。

Translates an error message in its default scope (activemodel.errors.messages).

https://api.rubyonrails.org/v7.0/classes/ActiveModel/Errors.html#method-i-generate_message

 

 
次に、定義されているシンボルを調べたところ、以下にありました。
4.5.2 エラーメッセージ内での式展開 | Rails 国際化(i18n)API - Railsガイド

 
これで必要な情報が集まったため、次は検証をしてみます。

 

検証

いくつかのパターンでどのように実装が異なるかを試してみます。

 

エラーメッセージの引数が不要な場合

シンボル :blank を使った、カスタムバリデーションメソッドを持つモデルを用意します。

class Shop < ApplicationRecord
  validate :validate_name

  private def validate_name
    return if name.present?

    errors.add(:name, :blank)
  end
end

 
Railsコンソールで試してみたところ、標準のエラーメッセージが表示されました。

>> shop = Shop.new
   (1.2ms)  SELECT sqlite_version(*)
=> #<Shop:0x00007fad5cfd9bc8 id: nil, name: nil, created_at: nil, updated_at: nil>
>> shop.valid?
=> false
>> shop.errors.full_messages
=> ["Name can't be blank"]

 

エラーメッセージの引数が必要な場合

シンボル :wrong_length では引数 count が必要になるため、これを引数のシンボルとして使ってみます。

class Shop < ApplicationRecord
  validate :validate_name

  private def validate_name
    return if name.present?

    errors.add(:name, :wrong_length, count: 5)
  end
end

 
Railsコンソールで確認すると、引数の値がメッセージに反映されていました。

>> shop = Shop.new
   (1.8ms)  SELECT sqlite_version(*)
=> #<Shop:0x00007fb234ad94c0 id: nil, name: nil, created_at: nil, updated_at: nil>
>> shop.valid?
=> false
>> shop.errors.full_messages
=> ["Name is the wrong length (should be 5 characters)"]

 

標準のバリデータで、標準メッセージを別の標準メッセージに差し替える

Blogタイトルとは異なりますが、標準バリデータのメッセージを標準に差し替えることも試してみます。

Railsガイドによると、 message オプションを使えば良さそうでしたので、試してみます。
3.3 :message | Active Record バリデーション - Railsガイド

 
モデルを変更します。

class Shop < ApplicationRecord
  validates :name, presence: { message: :wrong_length, count: 4 }
end

 
Railsコンソールで確認したところ、メッセージが差し替わっていました。

>> shop = Shop.new
   (1.0ms)  SELECT sqlite_version(*)
=> #<Shop:0x00007f9c02589148 id: nil, name: nil, created_at: nil, updated_at: nil>
>> shop.valid?
=> false
>> shop.errors.full_messages
=> ["Name is the wrong length (should be 4 characters)"]

 

ソースコード

Githubに上げました。