【Ajax通信】フォーム入力画面で非同期通信によるエラーメッセージ表示 ②

おはようございます。
今日は前回から続いて、Ajax通信を利用したフォーム送信機能の実装をまとめていけたらと思います。前回まででコントローラーへフォーム情報を送信するところまでは実装できています。
【Ajax通信】フォーム入力画面で非同期通信によるエラーメッセージ表示 ① - さかいの気ままにプログラミング日記

そこで今回はバリデーションを設定し、フォーム情報が適切でないときにエラーメッセージを表示させます。
実装後の挙動です。

gyazo.com


今回編集するのは、モデル、コントローラー、JavaScriptファイルです。
では順番に見ていきます。

モデル

ここではモデルへバリデーションをかけます。今回書けるバリデーションは3つだけです。

validates :artist, :first_song, presence: true
validates :artist, uniqueness: true


コントローラー

ここではjson形式でコントローラーへ送られてきたフォーム情報をテーブルへ保存する記載をします。

  def create
    @set_list = SetList.new(set_list_params)
    respond_to do |format|
      if @set_list.valid?
        @set_list.save
        format.html { redirect_to root_path }
        format.json { render json: @set_list.errors.full_messages }
      else
        format.json { render json: @set_list.errors.full_messages }
      end
    end
  end

  private
  def set_list_params
    params.require(:set_list).permit(
      :event_id,
      :artist,
      :first_song,
      :
     略
      :
      :tenth_song
    )
  end

復習も兼ねて基礎中の基礎からまとめていきます。(カリキュラムもあと一ヶ月弱で見れなくなるので・・)

@set_list = SetList.new(set_list_params)

SetListクラスのインスタンスを生成し、引数のset_list_paramsの中身をインスタンスに追加しています。
もっとイメージしやすい言葉に言い換えると、SetList.newで一行レコードを追加し、カラムにparamsで送られてくる値を代入しています。下の例だと分かりやすいと思います。

例)
SetList.new
=> #<SetList:0x00007fa9ea72d5f8
id: nil,
event_id: nil,
artist: nil,
first_song: nil,
:
:
updated_at: nil


SetList.new(artist: "sumika")
=> #<SetList:0x00007fa9eb879b40
id: nil,
event_id: nil,
artist: "sumika",
first_song: nil,
:
:
updated_at: nil

paramsの中身はこんな感じです。
f:id:shun_0211:20200523060438p:plain

ここで、セキュリティー上の問題に対処する手法としてストロングパラメーターを使用します。ストロングパラメーターを使用することで、指定したパラメーター以外のキーを受け取れなくします。
ストロングパラメーターを使用した際のset_list_paramsの中身はこんな感じになります。
f:id:shun_0211:20200523060443p:plain



(補足)
・paramsとはコントローラーに送られてくるデータが格納されている箱(ハッシュオブジェクト)のことです。
・private以下のメソッド全体をストロングパラメーターと呼びます。
・form_forやform_withを使用する際はrequire(:モデル名)が必要です。(ハッシュ構造の場合は記載が必要)

respond_to do |format| ~ end

respond_to メソッドを使い、リクエストフォーマット(HTML, JSON, etc...)に応じた処理の記述をしています。(今回はリクエストはJSONのみなので、format.htmlが走ることはありません。)
valid?がtrueでもfalseでもJavaScriptにはエラーメッセージを返すように記載しています。理由はJavaScript側での処理をエラーメッセージのありなしで分けているからです。
format.jsonの後には、JavaScriptに返すデータを記載します。ここでは、renderメソッドを使用して@set_list.errors.full_messagesをjson形式のデータに変換しています。
次ではJavaScript側での処理を詳しく見ていきます。


(補足)
json形式 ⇒ {"キー" : "バリュー", "キー" : "バリュー"}
・renderメソッドでのデータの変換

render json: @set_list    #@set_listをjson形式のデータに変換

・エラーメッセージのオブジェクト

@set_list.error.messages    #=> {:アーティスト名=>["を入力してください", "はすでに存在します"], :一曲目=>["を入力してください"]}
@set_list.error.full_messages    #=> ["アーティスト名を入力してください", "アーティスト名はすでに存在します", "一曲目を入力してください"]

・エラーメッセージの日本語化
i18nというGemを使いました。参考記事をみて簡単に実装することができました。

JavaScriptファイル

ここではコントローラーから値が返ってきた後の処理を.done以下に記載していきます。

中略
$.ajax({
      url: url,
      type: "POST",
      data: formData,
      dataType: 'json',
      contentType: false,
      processData: false
    })
    .done(function(error_messages){
      var error_num = Object.keys(error_messages)
      if (error_num == 0){
        window.location.href = `/events/${gon.event.id}/choise_artist`;
      }
      else{
        error_messages.forEach(function(error_message){
          var error_alert = `<div class = error_message>
                               * ${error_message}
                             </div>`
          $('.error_messages_box').append(error_alert);
          $('.error_messages_box').css({
            'padding': '30px 20% 0',
            'font-size': '13px',
            'color': 'red'
          })
        })
      }
    })
  })
})


バリデーションが通ったときの記述
.done(function(error_messages){
      var error_num = Object.keys(error_messages)
      if (error_num == 0){
        window.location.href = `/events/${gon.event.id}/choise_artist`;
      }

doneの後のfunctionの引数はコントローラーから返ってきた値です。
まず、Object.keysでオブジェクトのキーを取得します。今回のような配列の場合、[0, 1, 2 …]のように返します。これはエラーメッセージが返ってきているかどうかを判別するためです。もしエラーメッセージが返ってきていない場合、指定のURLへ遷移させています。

バリデーションが通らなかったときの記述
else{
        error_messages.forEach(function(error_message){
          var error_alert = `<div class = error_message>
                               * ${error_message}
                             </div>`
          $('.error_messages_box').append(error_alert);
          $('.error_messages_box').css({
            'padding': '30px 20% 0',
            'font-size': '13px',
            'color': 'red'
          })
        })
      }

エラーメッセージが通らなかった場合は、エラーメッセージをviewに表示させる記述を行います。エラーメッセージは配列で格納されているのでforEach文を使って一つ一つを、viewに予め作っておいたerror_messages_boxへappendしていきます。
そして同時に見た目を整えるCSSを付与しています。

終わり!


これで非同期通信によるエラーハンドリングが完了しました。非同期での実装以外にも同期通信やjavascriptだけでの実装も可能なようなので、挑戦していきたいと思います。
ここまで読んでくださり、ありがとうございました。
分かりにくいやアドバイス等ありましたらコメントくださると幸いです。では!



参考サイト
忘れがちなrenderメソッドの使い方まとめ [Rails] - Qiita
【Rails】バリデーションのエラーメッセージを表示する - Yohei Isokawa
Railsのバリデーションエラーのメッセージの日本語化 - Qiita