【Ajax通信】非同期でのコメント機能の実装

こんばんは
今日は個人アプリへコメント機能の実装を行ったので、それについてまとめていきたいと思います。

前提

 コメント機能を実装したいビューができていること

① フォームを作る

 まずは、ビューにフォームを作ります。

.comment__box
    .comment__contents
 .comment__form__box
    = form_for [@set_list, @comment], url: {controller: 'comments', action: 'create', set_list_id: '1'} do |f|
        = f.text_field :content, placeholder: "コメントを入力して下さい", class: "comment__form"
        = f.button type: "submit", class: "comment__post__button" do
           %i.far.fa-comment

 モデルをネストさせたルーティングを組んでいるので、インスタンス変数を配列の形で2つ記載しています。
しかし、このままだと下のようなエラーがでます。ActionController::UrlGenerationErrorはrailsがうまくURLを見つけれてないため起こるエラーで、ネストさせている場合に起こるようです。なので、rails にURLを指定してあげましょう。
 また、この際、set_list_idも渡してあげないとどのセットリストに対してコメントをするのか不明なため、こちらも指定します。


f:id:shun_0211:20200525202557p:plain


② JavaScriptでフォーム情報をJson形式でコントローラーに送信する。

 JavaScriptにフォーム情報をコントローラーに送信する処理を書いていきます。

$(function(){
  $('#new_comment').on('submit', function(e){
    e.preventDefault();
    var formData = new FormData(this);
    var url = $(this).attr('action');
    $.ajax({
      url: url,
      type: "POST",
      data: formData,
      dataType: "json",
      processData: false,
      contentType: false
    })
  })
})

e.preventDefault();

 e.preventDefault()はデフォルトのイベントを止める処理です。これがないとフォームを同期通信で送信してしまうのでこちらは入れます。

var formData = new FormData(this);

 formDataオブジェクトを作成しフォーム情報を入れ込んでいます。

var url = $(this).attr('action');

 Jqueryのattr()は属性を取得するメソッドでaction属性を取得しています。action属性は、フォームの送信データの送信先を設定するための属性なのでこれを取得しています。

$.ajax({ ~~ })

 ajax以下ではAjax通信するのに必要な情報を記載しています。


③ コントローラーでフォーム情報を保存し、javascriptへ返す処理を書く

 コントローラーではJson形式で送られてきたフォーム情報をデータベースへ保存し、またJavaScript側に返します。

def create
    @comment = Comment.new(comment_params)
    @comment.user_id = current_user.id
    @comment.set_list_id = params[:set_list_id]
    respond_to do |format|
      if @comment.save
        format.html { redirect_to :back }
        format.json
      end
    end
  end

  private
  def comment_params
    params.require(:comment).permit(:content)
  end


@comment.user_id = current_user.id

 user_idカラムへコメントを送信したユーザーのidを入れています。

@comment.set_list_id = params[:set_list_id]

 なぜかparamsの中にset_list_idが入っているのにも関わらず、ストロングパラメーターで持ってこれなかったのでこのような記載をしています。なぜとってこれないのかは調査中で分かったらブログにアップします。

respond_to do |format| ~ end

 リクエストフォーマットに応じた処理を書いています。
format.jsonの後に何も書いていないのはjbuilderを使用してJavaScriptファイルに返すデータを作るからです。
※ ここにrenderメソッドを使用した場合はdone(function(data)~~の引数がrenderメソッド指定した情報が格納されるので、何も書いてはいけません。

 入力したフォーム情報が辿る流れとしては、JavaScriptからフォーム情報がコントローラーへ送られ、その後、一旦json.jbuilderを経由してjavascriptへ戻ってきます。json.jbuilderではインスタンス変数からjson形式のデータを作ってくれます。次ではそのjson.jbuilderファイルを編集します。


④ create.json.jbuilderを作成し、json形式のデータを作成する。

 デフォルトではファイルは作られないので、views/commentsディレクトリにcreate.json.jbuilderファイルを作成します。ファイル名は該当するactionと同じ名前にする必要があります。必ずactionに紐付いたファイル名にしましょう。actionがjbuilderの名前を通じて紐付いているため、actionで定義したインスタンス変数を使うことができます。
 では、json.(キー) (バリュー)というように記載してjson形式のデータを準備していきます。

json.content @comment.content
json.user_id @comment.user.id
json.user_nickname @comment.user.nickname
json.created_at @comment.created_at.strftime("%Y年%m月%d日 %H:%M")


JavaScriptで返ってきた値を使って、HTMLを作成しviewに挿入

 最後はブラウザへコメントを反映させる作業になります。

$(function(){
  function buildHTML(comment){
    var html = `<div class = "comment__posted">
                  <div class = "comment__posted__information">
                    <div class = "comment__user__nickname">
                      ${comment.user_nickname}
                    </div>
                    <div class = "comment__post__time">
                      ${comment.created_at}
                    </div>
                  </div>
                  <div class = "comment__posted__content">
                    ${comment.content}
                  </div>
                </div>`
    return html;
  }
  $('#new_comment').on('submit', function(e){
    e.preventDefault();
    var formData = new FormData(this);
    var url = $(this).attr('action');
    $.ajax({
      url: url,
      type: "POST",
      data: formData,
      dataType: "json",
      processData: false,
      contentType: false
    })
    .done(function(data){
      var comment_html = buildHTML(data);
      $('.comment__contents').append(comment_html);
      $('.comment__form').val("");
      $('.comment__post__button').prop("disabled", false);
    })
  })
})

.done(function(data){ ~~

 done以降でコントローラーから返ってきた値を使った処理が書かれます。functionの引数dataはコントローラーから返ってきた値が格納されています。

var comment_html = buildHTML(data);

 上の方に書かれたbuildHTML関数を呼び出してコメントを挿入するためのブロックを作っています。

function buildHTML(comment){ ~~

 コメントを挿入するためのHTMLのブロックを作っています。改行をそのまま書けるテンプレートリテラル記法を利用しています。
 コントローラーから返ってきている値はcreate.json.jbuilderを経由した値なので、comment.user_nicknameでコメントしたユーザーのニックネームを取得することができています。

$('.comment__contents').append(comment_html);

 先程作ったコメントを挿入したブロックをveiwに挿入しています。

$('.comment__form').val(""); と $('.comment__post__button').prop("disabled", false);

 コメントフォームを空にする処理と再度ボタンをクリックできるようにする処理です。

終わり!!


ここまで読んでくださり、ありがとうございました。
form_forでのネストしたルーティングの場合、URLを指定しないといけない点やストロングパラメーターでset_list_idがとってこれない点に少し苦戦しましたが、それ以外は特に躓くことは少なかったです。時間はかかりましたが、、
分かりにくいやアドバイス等ありましたらコメントくださると幸いです。では!