Railsでフォームを作っていて、バリデーションのエラーメッセージをまとめて表示するのではなく、各パーツの下に個別表示したい…というニーズがありました。実際、上にエラーを表示するよりも該当箇所に表示したほうがユーザビリティが向上しそうです。
エラーメッセージは、@form.errors.full_messages
などに格納されているので頑張ればなんとかできそうです。というわけで、2つほどやり方を試してみました。
field_error_procをカスタマイズ
わりとよく見る解決方法です。
config/initializers/error_customize.rb
あたりにActionView::Baseのfield_error_proc
にProcをブチ込みます。
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
if instance.kind_of?(ActionView::Helpers::Tags::Label)
html_tag.html_safe
else
method_name = instance.instance_variable_get(:@method_name)
errors = instance.object.errors[method_name]
html = <<~EOM
<div class="has-error">
#{html_tag}
<span class="help-block">
#{I18n.t("activerecord.attributes.#{instance.object.class.name.underscore}.#{method_name}")}
#{errors.first}
</span>
</div>
EOM
html.html_safe
end
end
これはこれでエラー文言を割りと自在に操れるんですが、collection_radio_buttons
collection_select
などの複数のフォームパーツを表示するようなヘルパーを使うと、パーツ分エラー文言が表示されてしまいます。
フォームビルダー
RailsのフォームオブジェクトはFormBuilderというクラスのインスタンスです。そしてサブクラスとして拡張することができます。これを使ってエラー文言をコントロールします。
app/form_builders/custom_form_builder.rb
あたりにファイルを作ります。
元から定義されているヘルパーメソッドをオーバーライドしてエラーオブジェクトがあれば、それを表示し、それ以外の処理はsuper
で任せるようにしています。下記コードは全てのフォームヘルパーを再定義しているわけじゃないので必要に応じて拡張してください。
class CustomFormBuilder < ActionView::Helpers::FormBuilder
def text_field(method, options = {})
super + error(method)
end
def number_field(method, options = {})
super + error(method)
end
def telephone_field(method, options = {})
super + error(method)
end
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
super + error(method)
end
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
super + error(method)
end
private
def error(method)
error_html(error_message(method))
end
def error_message(method)
(@object.errors[method].size > 0) ? I18n.t("activerecord.attributes.#{@object.model_name.singular}.#{method}") + @object.errors[method].first : ""
end
def error_html(msg)
return "" unless msg.present?
@template.content_tag(:div, class: "has-error") do
@template.concat (@template.content_tag(:span, class: "help-block") do
msg
end)
end
end
end
そして使いたいフォームのViewで拡張したフォームビルダーを指定します。
= form_for @form, :builder => CustomFormBuilder do |f|
これで完了です。簡単ですね。