Railsでformのfield毎にエラーメッセージを表示させる便利設定


備忘録的な内容なので参考までに。
設定の設置場所はconfig/initializers/error_customize.rb

ActionView::Base.field_error_proc = proc do |html_tag, instance|
  if instance.is_a?(ActionView::Helpers::Tags::Label)
    html_tag
  else
    method_name = instance.instance_variable_get(:@method_name)
    errors = instance.object.errors[method_name]
    view = ActionView::Base.new
    field_tag = []
    field_tag << html_tag
    field_tag += errors.map do |error|
      view.content_tag(:div, class: "help-block") do
        "#{instance.object.class.human_attribute_name(method_name)}#{error}"
      end
    end
    view.content_tag(:div, class: "has-error") do
      view.safe_join(field_tag)
    end
  end
end

参考にしたQiita記事: https://qiita.com/zaru/items/54cd20520feda61f5332

自分で手を加えたポイント

このままだと、ただ参考にした記事をパクッてるだけやん、となりそうなので上記の記事を参考に自分なりに修正を加えた箇所について解説をします。

ActionView::Base.field_error_proc = proc do |html_tag, instance|
  if instance.is_a?(ActionView::Helpers::Tags::Label)
    html_tag

最初のif文のところで、html_tag.html_safeという記載にhtml_tagに書き換えました。理由は、私のRubocop先生からRuboCop::Cop::Rails::OutputSafetyの指摘を喰らったため。ただし、単純にhtml_safeを外しただけでは根本解決になっていのでは?という疑問を持たれそうなので一応補足をすると、pryで覗いたときにこんな感じの挙動をしていることがわかりました。

はい、もともとActiveSupport::SafeBufferクラスのインスタンスなのでhtml_safeは不要なんですね。ということで削除しても問題なさそうということがわかったので削除しました。

  else
    method_name = instance.instance_variable_get(:@method_name)
    errors = instance.object.errors[method_name]
    view = ActionView::Base.new
    field_tag = []
    field_tag << html_tag
    field_tag += errors.map do |error|
      view.content_tag(:div, class: "help-block") do
        "#{instance.object.class.human_attribute_name(method_name)}#{error}"
      end
    end
    view.content_tag(:div, class: "has-error") do
      view.safe_join(field_tag)
    end
  end

後半のelse文のところについては、ごっそりと書き方を変えています。ポイントは2つ。

1つ目は、こちらも同じくrubocop先生からRuboCop::Cop::Rails::OutputSafetyの指摘を喰らっていたので、RuboCop::Cop::Rails::OutputSafetyのGoodパターンであるsafe_joinにて最終的にHTMLを吐き出す流れを取った点です。

2つ目は、元のコードの場合、発生しているバリデーションエラーの内、最初のエラーのみしか取れないロジックになっていたので、errors.eachのところですべてのエラーをHTMLとして吐き出す処理を加えた点です。

まとめ

これで根本的なバグ(最初のエラーしか取れないバグ)は解決し、Rubocop先生からも指摘をされないコードになりました。この設定自体はかなり便利な設定だなぁと思うので、興味がある方はぜひ使ってみてください。大本のロジックについては自分で考えることができなかったので参考にさせて頂いた記事には感謝です。