アルパカログ

プログラミングとマネジメントがメインです。時々エモいのも書きます。

【Rails5】カスタムバリデーションのエラーメッセージをi18nに対応する

Ruby on Rails 5 で、カスタムバリデーションを追加したときに、エラーメッセージ(errors.full_messages)の翻訳で少々ハマったのでまとめておきます。

通常のモデルのバリデーションであれば、次のようにロケールファイルを定義しておくことで、errors.full_messages が翻訳された状態で得られます。

ja:
  errors:
    messages:
      blank: を入力してください
      taken: はすでに存在します
    full_messages:
      format: "%{attribute}%{message}"
  attributes:
    email: メールアドレス

例えば、user.email フィールドが presence: true でエラーになった場合は、blank にあたる「メールアドレス を入力してください」というエラーメッセージになります。

uniqueness: true の場合は taken です。他の翻訳の種類は下記から探してみてください。

Ruby on Rails 5で独自のカスタムバリデーションを追加するためには、ActiveModel::Validator または ActiveModel::EachValidator を継承した Validator クラスを実装する必要があります。

Ruby on Rails ガイドには次のような例が載っています。

class MyValidator < ActiveModel::Validator
  def validate(record)
    unless record.name.starts_with? 'X'
      record.errors[:name] << '名前はXで始まる必要があります'
    end
  end
end
 
class Person
  include ActiveModel::Validations
  validates_with MyValidator
end
class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors[attribute] << (options[:message] || "はメールアドレスではありません")
    end
  end
end
 
class Person < ApplicationRecord
  validates :email, presence: true, email: true
end

どちらも record.errors にエラーメッセージを追加していますが、i18n に任せようとすると上手くいきません。

errors.full_messages を翻訳された状態で得るためには、add メソッドを使って次のようにします。

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors.add(attribute, :not_email_format)
    end
  end
end

add メソッドの第1引数はオブジェクトの属性です。ActiveModel::Validator を継承しているなら対象の属性名のシンボル(:email:name) としてください。

第2引数の :not_email_format に対応する翻訳を errors に追加します。

ja:
  errors:
    messages:
      blank: を入力してください
      taken: はすでに存在します
      not_email_format: がメールアドレスの形式ではありません

こうしておくことで、EmailValidator に引っかかったとき、errors.full_messages が「メールアドレス がメールアドレスの形式ではありません」と翻訳された状態で得られます。