適当おじさんの適当ブログ

技術のことやゲーム開発のことやゲームのことなど自由に雑多に書き連ねます

Railsのフォームビルダーをカスタマイズして、入力フォームのそばにエラーメッセージを表示する

RailsActiveRecordを使っていてバリデーションエラーが発生した場合に、Modelの errors に各種エラーメッセージが格納されます。

errorsを回してエラーを表示するだけであれば簡単ですが、下図のように入力フォームのそばにエラーメッセージを表示しようとすると少し面倒です。

このような入力フォームとエラーメッセージにするためには、テンプレートに以下の処理を追加しなければなりません。

  1. フォームの枠の色を変えるための条件分岐
  2. エラーメッセージを表示するための条件分岐とHTMLのタグ

パラメータが1つだけであればそんなに手間ではありませんが、複数ある場合はすべてのフォームに1と2を追加していかなければなりません。

対応方針

1と2を追加する手間を省くために、入力フォーム作成時に自動的に関連するエラーメッセージも作成されるフォームビルダーを実装します。

「エラーメッセージがあるときに〜」の条件分岐の記述がすべてフォームビルダークラスに移るため、テンプレートがスッキリします。

事前知識

独自のフォームビルダーは、以下のようにFormBuilderクラスを継承することで定義できます。

class FormWithErrorMessageBuilder < ActionView::Helpers::FormBuilder
    # 独自の処理を記述
end            

また、処理の過程でHTMLタグを生成したい場合には、content_tag などの専用メソッドを用います。divブロックを新たに作成したい場合は、以下のように記述します。第三引数のオプションには、id や class などのHTMLの属性を指定できます。

# 第一引数にセレクター、第二引数に内容、第三引数以降にオプション
@template.content_tag(:div, "エラーメッセージ", class: "error-class")

# ブロック形式の記述も可能、タグが入れ子になる場合はこちらで
@template.content_tag(:div, class: "error-class") do
   エラーメッセージ
end

今回は、エラーがある場合のみエラーメッセージを表示したいです。なのでエラーがある場合には、上記のcontent_tagを使ってエラーメッセージのHTMLタグを動的に追加します。

実装

最終的に以下のような独自のフォームビルダーを実装しました。error-classの部分には、エラー発生時のcssクラスを自由に設定してください。

# form_helper.rb
module FormHelper                                                               
  class FormWithErrorMessageBuilder < ActionView::Helpers::FormBuilder
    # 従来のフォームに加えて、エラーがある場合にエラーメッセージを表示するメソッド
    def input_field_with_error(attribute, options={}, &block)   
      # 入力フォームと同じ属性のエラーメッセージを取得する                
      error_messages = @object.errors.full_messages_for(attribute) 

      # エラーがある場合のみ、エラー用のHTMLにする             
      if error_messages.any?                                                    
        options[:class] << "error-class"                                        
        error_contents = create_error_div(attribute, error_messages)            
      end

      # 従来の入力フォーム と 生成されたエラーメッセージ を連結して返す                                                                                                                                                 
      block.call + error_contents || ""                                         
    end                                                                         
                                                                                
    # エラーメッセージのHTMLタグを作成する
    def create_error_div(attribute, messages)    
      # content_tag でHTMLタグを生成                               
      @template.content_tag(:div, class: "error-class") do
        messages.each do |message|                                              
          @template.concat(@template.content_tag(:div, message))                
        end                                                                     
      end                                                                       
    end

    # 既存のビューヘルパーメソッドをオーバーライドする
    def text_field(attribute, options={})                                       
      input_field_with_error(attribute, options) do                             
         super                                                                   
      end                                                                       
    end                                                                                                                                                                           
  end                                                                         
end                                                                               

オーバーライドしたビューヘルパーの使用時に属性名が input_field_with_error に受け渡され、入力フォームとエラーメッセージが対応づけられます。見かけ上はRails標準のビューヘルパーを使っているだけですが、 input_field_with_error がかまされているのでエラーがある場合には関連するエラーメッセージが表示されます。

この例では、text_field のみエラーメッセージが表示されるようになっています。それ以外の入力フォームにもエラーメッセージを表示したい場合は、同様に既存のビューヘルパーメソッドをオーバーライドすればよいです。

email_field, password_field にもエラーメッセージが表示されるようにしましょう。 input_field_with_error をかますだけなので簡単にできます。

def email_field(attribute, options={})                                      
  input_field_with_error(attribute, options) do                             
    super                                                                   
  end                                                                       
end                                                                                                                                                  
def password_field(attribute, options={})                                   
  input_field_with_error(attribute, options) do                             
    super                                                                   
  end                                                                       
end 

独自のフォームビルダーを使うように設定する

フォーム作成時のビューヘルパーの引数に :builder => FormHelper::FormWithErrorMessageBuilder を指定するなどして、独自のフォームビルダを使うための設定をお忘れなく。