13
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-01-23

アソシエーション先のカスタムエラーメッセージとして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: '' # ここを空文字にするのがポイント

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

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されており、修正は結局出なかった模様...
背景が知りたい.

13
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?