バリデーションのエラーメッセージの躓きをまとめてみた。


はじめに

超初学者です。

サインアップ時に入力してもらう項目にバリデーションをかけて、ユーザビリティ向上のため、どこを修正すべきかというエラーメッセージを入力ページに表示させるように実装してみました。
その際に色々な障害にぶつかり、時には初歩的なミスで躓きましたが、何とか形にすることができました。

ここでは、私がどのようなエラーに遭遇し、乗り越えてきたかの経緯を簡単にお伝えできればと思います。
「もっとこうした方がいいよー!」というご指摘は大歓迎です!

では、よろしくお願いします。

バージョン情報

  • Ruby 2.5.1
  • Rails 5.2.3
  • Haml・Sass記法

エラーだらけの入力ページ

まず、エラーメッセージを入力ページ上部に表示させる方法を採用し実装を進めて行きました。
エラーメッセージの表示の仕方としては、次の記事を参考にしています。
バリデーションのエラーメッセージの表示の仕方(Rails)

sample.html.haml
.signup-panel
  %p.signup-panel__title 会員情報入力
     = form_for @user, url: ●●●●_●●●●_path, html: {class: "inputForm"} do |f| 
       = render 'shared/error_message', model: f.object 
       .signup-form
         .field 
           = f.label :nickname, "ニックネーム" 
shared/error_message.html.haml
 = if model.errors.any?
   .alert.alert-warning 
     = model.errors.full_messages.each do |message| 
       %li 
         = message 

ここではエラーが発生した時の条件分岐をパーシャル(部分テンプレート)として切り取り、renderで入力ページ上部に呼び出すようにしています。

第二引数でパーシャルに渡しているmodel: f.objectはform_forメソッドの引数で指定している@userと同義です。f.object@userとしても問題は無いみたい。
ただ、f.objectの方が他のフォームでもそのまま使い回せて便利そうだなぁという感じでした。

とりあえず、上記のコードでビューを表示させてみます。
すると・・・

ドウシテコウナッタ・・・

はい。もうエラーで滅茶苦茶ですね。直すところしかない。

修正箇所は4つ

今回直すべきは以下の項目ですね。

  • 入力フォームのCSSが崩れてしまっている。
  • エラーメッセージが表示される枠(黄色い部分)からはみ出た文字がある。
  • エラーメッセージが重複している。
  • エラーメッセージが一部だけ英語表記になっている。

では、早速取り掛かっていきます!

1つ目

CSSが崩れた原因を探るため、デベロッパーツールで確認したところ、field_with_errorsというクラスが自動生成されており、それが既存のCSSを妨害していることが判明しました。
フォームヘルパーで入力フォームを実装すると、バリデーションに引っ掛かった項目に自動で作られるクラスのようです。

対処法としては下記を参考にしました。
Railsのバリデーションエラーで、「field_with_errors」によるレイアウト崩れを防ぐ

sample.scss
.field_with_errors {
  display: contents;
  // もしくは diplay: inline;
}

field_with_errorsにCSSを当てました。

実行結果はこんな感じ。

とりあえず入力フォームのエラーは解決しました!

2つ目

メッセージが予期せぬ表示になっている部分について。

パーシャルで切り取った部分をよくよく確認すると、= if model.errors.any?でミスがありました。

決定版!!Haml小技まとめ!!

Haml記法では、rubyのコードを書きたい時、eachやifといったロジック部分の先頭には-をつけます。
また、rubyで出力した結果をビューにも出力したい時には、先頭に=をつけます。

今回のエラーはロジック部分に=をつけていたことで、ビューに無駄な出力がされていたことが原因みたいでした。

app/views/shared/error_message.html.haml
 - if model.errors.any?
   .alert.alert-warning 
     = model.errors.full_messages.each do |message| 
       %li 
         = message 

上記のように修正してみました!

3つ目

エラーメッセージが重複している問題も、2つ目と同じ理由で解決できそうですね。

app/views/shared/error_message.html.haml
 - if model.errors.any?
   .alert.alert-warning 
     - model.errors.full_messages.each do |message| 
       %li 
         = message 

これでエラーメッセージはスッキリしました!

4つ目

バリデーションのエラーメッセージを日本語にする方法としては、既に色々な記事があるのでここでは割愛しますが、個人的に分かりやすいなぁと感じたのは次の記事でした。
RailsのActiveRecord, ActiveModelのバリデーションエラーメッセージを日本語化

記事どおりに実装すれば、ここは簡単にできます。

これでエラーは全て解決しました。いやー、めでたしめでたし。

おまけ(むしろメイン?)

エラーメッセージを実装していて、ふと「入力フォーム直下にエラーメッセージ出した方がユーザビリティあるし、スタイリッシュじゃない?」と思いまして、興味本位で実装してみました。

実装したビューがこちら。

中々カッコいいですよね!!

というわけで、折角なので実装方法をご紹介します。

参考記事↓
FormBuilderを使って楽チンにエラーメッセージを表示しよう(メモ)

以下、実際に書いたコードです。

helpers/with_error_form_builder.rb
class WithErrorFormBuilder < ActionView::Helpers::FormBuilder

  def pick_errors(attribute)
    return nil if @object.nil? || (messages = @object.errors.messages[attribute]).nil?

    lis = messages.collect do |message|
      %{<li>#{@object.errors.full_message(attribute, message)}</li>}
    end.join

    %{<ul class="errors">#{lis}</ul>}.html_safe
  end

  def text_field(attribute, options={})
    return super if options[:no_errors]
    super + pick_errors(attribute)
  end

  def email_field(attribute, options={})
    return super if options[:no_errors]
    super + pick_errors(attribute)
  end

  def password_field(attribute, options={})
    return super if options[:no_errors]
    super + pick_errors(attribute)
  end

  def date_select(attribute, options={})
    return super if options[:no_errors]
    super + pick_errors(attribute)
  end
end

まず、独自のフォームビルダーを作ります。私はapp/helpersの下に実装しました。
pick_errorsアクションでは、エラーメッセージを含んだ要素を生成する記述をしています。@objectは今回は@userと同義みたいです。@object若しくはエラーメッセージが無ければnilを返します!
html_safeメソッドは「<」「>」「&」「"」といった特別な意味を持つ文字をエスケープ処理せずにそのまま出力してくださいねーといったものになります。なので、「errorsというクラスを作って、そこにエラーメッセージを出してください!」といった記述になります。

text_field以下については、既存のフォームヘルパーをオーバーライド(親クラスにあるメソッドを子クラスで再定義することによって、子クラス上で親クラスのメソッドを上書き)させるための処理をします。使っているfieldの種類によって、その分記述を追加する必要があります。

その後、独自で作成したフォームビルダーを適用させるための記述をしていきます。

sample.html.haml
.signup-panel
  %p.signup-panel__title 会員情報入力

     = form_for @user, url: ●●●●_●●●●_path, builder: WithErrorFormBuilder, html: {class: "inputForm"} do |f| 
       = render 'shared/error_message', model: f.object 
       .signup-form
         .field 
           = f.label :nickname, "ニックネーム" 

あとはバリデーションがかかった際に作成されるクラスfield_with_errorsにCSSを当てて完成です!

sample.scss
 // エラーメッセージ
.field_with_errors{
 display: inline;
}          
.field_with_errors input,
.field_with_errors select{
  outline: 0;
  border-color: $red !important;
}
.errors{
  margin-top: 8px;
  color: $red;
}

※注意!
バリデーションがかかることで発生するCSSの崩れを直すため、application.rbに下記の記述をしているとfield_with_errorsが自動生成されないので、フォームの色を変えたりが出来なくなってしまうので気をつけてください。

application.rb
module SampleApp
  class Application < Rails::Application
    config.action_view.field_error_proc = Proc.new { |html_tag, instance| html_tag }

  end
end

終わりに

ユーザー周りの実装をメインに行ってますが、まだまだわからないことが多々あるなぁという感じです。初歩的な部分でミスすることもあるので、こうしたミスを減らせるようにしたいです。

参考記事

バリデーションのエラーメッセージの表示の仕方(Rails)
Railsのバリデーションエラーで、「field_with_errors」によるレイアウト崩れを防ぐ
決定版!!Haml小技まとめ!!
RailsのActiveRecord, ActiveModelのバリデーションエラーメッセージを日本語化
FormBuilderを使って楽チンにエラーメッセージを表示しよう(メモ)