[Rails]コメント機能のAjax(非同期通信)について

#はじめに
ポートフォリオ用の作品にコメント機能を導入したのですが、非同期通信になってなかったのでストレスなく投稿出来るようにしようと思い実装しました。
###環境
ruby 2.5.1
Rails 5.2.3
##Ajax(非同期通信)とは

Ajaxとは「Asynchronous JavaScript + XML」の略で簡単にいうと[JavaScriptとXMLを使って非同期にサーバとの間の通信を行うこと]です。

具体的な説明はこちらがわかりやすいかと思いますので参考にして下さい。
https://qiita.com/hisamura333/items/e3ea6ae549eb09b7efb9

##実装方法
###導入について
モデルやコントローラーの作成手順は省略します。
先ずはGemなどの確認から使用するのは`gem 'jquery-rails'`です。もし無ければ先ずは`Gemfile`に記述し、`Bundle install`します

```ruby:Gemfile
gem 'jquery-rails'
```
次にapplication.jsに`//= require jquery`と`//= require rails-ujs`と`//= require turbolinks`があるのを確認します。これでjQueryは使用できるかと思います。

```ruby:application.js
//= require jquery
//= require rails-ujs
//= require turbolinks
```
###ビュー部分
先ずはビュー画面から今回はform_withを使用してます。非同期を導入するのに気をつけるのは`form_with`内に`id:名前`を追加する事です。その他はコメント機能の話になりますので割愛します。

```ruby:show.html.haml
.message
  .message-field
    - if current_user
      = form_with url: "/videos/#{@video.id}/comments", method: :post, id: "new_comment" do
        %h4.message-field__title *************************コメント入力欄*************************
        %textarea{name: "text", cols: "30", rows:"2", placeholder: "コメントを入力して下さい。(30文字以内)", class: "message-field__area",maxlength: "30"}
        %input{type: "submit", value: "投稿する", class: "btn btn-default"}
        %h4.commentlist＜コメント一覧＞
        .comments
          = render partial: "comments/comment"
```
###部分テンプレート化したビュー部分
```ruby:_comment.html.haml
- if @comments
  - @comments.each do |comment|
    = link_to comment.user.name, "/users/#{comment.user_id}",class: "username"
    .comment-style
      = comment.text
```
コメント機能や部分テンプレートについては以下を参考にして下さい。

#####コメント機能
https://qiita.com/nojinoji/items/2034764897c6e91ef982
#####部分テンプレート
https://qiita.com/takeru56/items/299850d0f054ce107e21

###コントローラー部分

```ruby:comments_controller.rb
class CommentsController < ApplicationController
  def create
    @comment = Comment.create(text: comment_params[:text], video_id: comment_params[:video_id], user_id: current_user.id)
    respond_to do |format|
      format.html { redirect_to video_path(params[:video_id])  }
      format.json
    end
  end
```
非同期に必要な部分は`respond_to do`の部分です。この記述をする事でリクエストされた`format`によって処理を分けるようにしてます。あとは`jbuilder`を使用し、JavaScript(jQuery)に返す値を作成します。

```ruby:create.json.jbuilder
json.text  @comment.text
json.user_id  @comment.user.id
json.user_name  @comment.user.name
```

jbuilderファイルでは`json.KEY VALUE`という形で書きます。
こうすることによってJavaScriptファイルに返ってきたデータをjbuilderで定義したキーとバリューの形で呼び出して使うことが出来るようになります。

###jQuery部分

次にJSファイルに非同期に必要な記述をします。

```javascript:comment.js
$(document).on('turbolinks:load', function(){
  function buildHTML(comment){
    var html = `
                <div class ="username">
                <a href=/users/${comment.user_id}>${comment.user_name}</a>
                <div class="comment-style">
                  ${comment.text}`
    return html;
  }
  $('#new_comment').on('submit', function(e){
    e.preventDefault();
    var formData = new FormData(this);
    var href = window.location.href + '/comments'
    $.ajax({
      url: href,
      type: "POST",
      data: formData,
      dataType: 'json',
      processData: false,
      contentType: false
    })
    .done(function(data){
      var html = buildHTML(data);
      $('.comments').prepend(html);
      $('.message-field__area').val('')
    })
//↓エラー時の処理
    .fail(function(){
      alert("error");
    })
  })
})
```
先ず、Railsのバージョンが5以降の場合jQueryを動かすために`$(document).on('turbolinks:load', function()`をはじめに記述します。
詳しい説明はこちらも参考にして下さい
https://qiita.com/hiroyayamamo/items/b258acbaa089d9482c8a

###var html部分
```javascript:comment.js
function buildHTML(comment){
    var html = `
                <div class ="username">
                <a href=/users/${comment.user_id}>${comment.user_name}</a>
                <div class="comment-style">
                  ${comment.text}`
    return html;
  }
```

```ruby:_comment.html.haml
- if @comments
  - @comments.each do |comment|
    = link_to comment.user.name, "/users/#{comment.user_id}",class: "username"
    .comment-style
      = comment.text
```
この部分ではjQueryのイベントが発火した時に表示させるhtmlを`var html`内に記述しています。書き方としてはビューファイルを確認してクラス名などを合わせるだけです。

###ajax部分
```javascript:comment.js
$('#new_comment').on('submit', function(e){
    e.preventDefault();
    var formData = new FormData(this);
    var href = window.location.href + '/comments'
    $.ajax({
      url: href,
      type: "POST",
      data: formData,
      dataType: 'json',
      processData: false,
      contentType: false
    })
```
この部分では先ほどビューファイルに記述した`id:`を`$('#new_comment')`に記述し送信ボタンを押した時`on('submit', function(e)`でイベントが発火します。
`$.ajax`の部分ではjson型で非同期通信をした際に保存される処理を行なっています。

###非同期通信成功時と失敗時の処理
```javascript:comment.js
.done(function(data){
      var html = buildHTML(data);
      $('.comments').prepend(html);
      $('.message-field__area').val('')
    })
//↓エラー時の処理
    .fail(function(){
      alert("error");
    })
```

`.done(function(data)`を使用して非同期通信成功時の処理を記述してます。
`var html`でhtmlの読み込み、`$('.comments').prepend(html);`では降順でコメントを表示させています。最後に`$('.message-field__area').val('')`で入力した文字を削除しています。

非同期通信に失敗した場合は`alert("error")`を使用しエラーメッセージを表示させています。
あとはクラス名やcssを調整し見た目を整えたら上手くいくと思います。

####成功時
![169e5646ffc352c2ad40de5bc9d30cfa.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/435517/c894daeb-dd6f-0846-fac4-47cc29770216.gif)
###失敗時
![d27650451185ff90a0afd3a3df530bf7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/435517/f5c166e9-a5a0-fe3e-270b-a7e498bd8cd9.gif)


#最後に
私も最初はクラスの当て方や、ビューの崩れの調整に結構時間がかかっていました。非同期に関しては慣れれば応用が効くようになるかと思います。
説明不足な部分もあるかと思いますので、以下も参考にして下さい。
https://qiita.com/ryosuke071111/items/bedb7f89fe63afc376d4
https://qiita.com/mikan3rd/items/21d716026b51331e53a1