Rails
I18n

関連先baseのエラーを関連元のfull_messagesで表示しようとするとおかしなことになる。

アソシエーション先のカスタムエラーメッセージとしてbaseのエラーをアソシエーション元のfull_messagesで表示しようとするとおかしなことになる。

具体的には、

# parent has_one childの関係があるとし、(has_manyでもよいが便宜上has_oneにしてる)
# 今childにカスタムバリデーションエラーがあるとする。
class Child < ApplicationRecord
    #...
    def hoge_validation
        errors.add(:base, :some_validation)
    end
    #...
end

# そのときparentからfull_messagesを呼ぶと...
parent.errors.full_messages
# => [ "Child basesome_validation_message" ]
# このように "モデル名 エラー属性" がエラーメッセージの先頭に含まれてしまう
# (これを避けたくてカスタムバリデーションにしたのに...)


# 直接childからfull_messagesを呼ぶと...
parent.child.errors.full_messages
# => [ "some_validation_message" ]
# 本来こうなって欲しい

なんやこれと思ってfull_messagesの実装を見ると...

# なるほどfull_messageでメッセージを生成している
def full_messages
  map { |attribute, message| full_message(attribute, message) }
end

# attributeが:baseならただメッセージを返す。もしや...
def full_message(attribute, message)
  return message if attribute == :base
  attr_name = attribute.to_s.tr(".", "_").humanize
  attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
  I18n.t(:"errors.format",
    default:  "%{attribute} %{message}",
    attribute: attr_name,
    message:   message)
end

attributeが:baseならただメッセージを返す、というところでもしや...と思いerrorsを確認すると、

# parent.errors.detail
=> {:"child.base"=>[{:error=>:some_validation}]}

# parent.child.errors.details
=> {:base=>[{:error=>:some_validation}]}

アソシエーション元のparentからみると、childのbaseにセットされたエラーは:"child.base"に変更されるため、full_messageの1行目でreturnされなくなっているようだ。

解決策を諸先輩に伺ったところ、localeファイルにbase属性を追加して、それを空文字にすればひとまず動きそうと分かった。

# parent.ja.yml
ja:
  activerecord:
    models:
      parent: アソシエーション元
    attributes:
      parent:
        base: '' # ここを空文字にするのがKIMO

これで理想通りの動きになった。

full_messageの実装を見るに、 "%{attribute} %{message}" の頭半分に空文字入ることで想定した表記になるのかな...でもそれだと空白が先頭に入って嫌だなと思ったところ、 gemとしてrails-i18nを入れておくと、rails-i18n自体が持っているlocaleファイルのja.ymlに

  errors:
    format: "%{attribute}%{message}"

とあって空白問題も解決。
gemを入れて無くとも自前のja.ymlに上記を追記すればOK.

とりあえずこれで対応することにするけど、なんとなくhackyでもやっとする。

参考

https://github.com/rails/rails/issues/19863
https://github.com/rails/rails/pull/20962

該当箇所の修正issueとprもあるがcloseされており、修正は結局出なかった模様...
背景が知りたい.