【Rails】【Ajax】コメント投稿・削除を非同期通信化する。

# はじめに
プログラミング初学者の者です。オンラインでプログラミングを学習させて頂いてるのですが、さらに理解を深めていきたいと思い、学習したことを備忘録として、残して理解を深めていきたいと思います。
また、間違った点など、ありましたら、ご指摘いただけると幸いです。よろしくお願いします。

# 前提
Railsにて投稿アプリケーションを作成のち、投稿に対して、コメントできる機能実装済み。
今回は離乳食を投稿するアプリケーションで実装しています。

# 目標
### コメント機能が非同期で行えるようにする
[![Image from Gyazo](https://i.gyazo.com/77a19537f3913ba1aae0c363e79f5193.gif)](https://gyazo.com/77a19537f3913ba1aae0c363e79f5193)

# 手順
①jQueryの導入
②コメント表示部分を部分テンプレート化
③投稿機能の非同期化
④削除機能の非同期化

# ①jQueryの導入
```text.Gemfile
---------略---------
gem 'mini_magick'
gem 'image_processing', '~> 1.2'
gem 'pry-rails'
gem 'devise'
gem 'ransack'
gem 'active_hash'
gem 'jquery-rails' //追記する
```
次にアプリケーションのターミナルで下記コマンドを実行します。
```console
% yarn add jquery
```
jQueryが追加されているか`⌘＋F`の検索ボックスを用いて確認します。「jQuery」と入力すると記述されていつファイルが見つかるので
`・package.json => "jquery": "^*.*.*"`
`・yarn.lock => jquery@^*.*.*:`
上記、二つにファイルが見つかれば確認完了です。
次に`environment.js`にコードを追加します。
```config/webpack/environment.js
const { environment } = require('@rails/webpacker')

// 追加ここから
const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)
// 追加ここまで

module.exports = environment
```
application.jsにコードを追記
```app/javascript/packs/application.js
require("@rails/ujs").start()
// require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("jquery")　　//このコードを追記
require("../recipe");
```
ここまでで、jQueryの導入は完了です。
# ②コメント表示部分を部分テンプレート化
```app/views/recipes/show.html
  <div class="recipe-comments">
----------ここから下----------
      <% @comments.each do |comment| %>
      <div class="comments_list">
        <div class="comments-title">
        <%= comment.user.user_name %>さんの投稿
        </div>
        <div class="comments-items">
         <div class="content">
          <%= comment.content %>
         </div>
         <div class="comment-delete">
          <% if current_user == comment.user %>
           <%=link_to "削除", recipe_comment_path(comment.recipe,comment), method: :delete, class:"comment-delete" %>
          <% end %>
         </div>
        </div>
      </div>
      <% end %>
----------ここまでを切り取る----------
    <div id="comments"><%= render 'comments/comments', comment: @comment %></div>　　　///このコードを追記
      <%= form_with model: [@recipe, @comment], local: true do |f| %>
      <div class = 'form-group'>
        <%= f.text_area :content, class: "form-control", id:"comment_content", placeholder: "コメントを記入してください" %>
      </div>
        <%= f.submit "コメントする", class: "btn btn-primary" , id: "submit" %>
      <% end %>
  </div>
</div>
<%= render "shared/footer" %>

```
views/commentsディレクトリの配下に`_comments.html.erb`を作成して先ほどの切り取りを貼り付ける
```app/views/comments/_comments.html
<% @comments.each do |comment| %>
  <div class="comments_list">
    <div class="comments-title">
      <%= comment.user.user_name %>さんの投稿
    </div>
    <div class="comments-items">
      <div class="content">
        <%= comment.content %>
      </div>
      <div class="comment-delete">
      <% if current_user == comment.user %>
        <%=link_to "削除", recipe_comment_path(comment.recipe,comment), method: :delete, class:"comment-delete" %>
      <% end %>
      </div>
    </div>
  </div>
<% end %>
```
# ③投稿機能の非同期化
`form_with`の`local: true`を外し`create.js.erb`を探しにいくようにする。
```show.html
 <div class="recipe-comments">
    <div id="comments"><%= render 'comments/comments', comment: @comment %></div>
      <%= form_with model: [@recipe, @comment] do |f| %>  ///local: trueを外す
      <div class = 'form-group'>
        <%= f.text_area :content, class: "form-control", id:"comment_content", placeholder: "コメントを記入してください" %>
      </div>
        <%= f.submit "コメントする", class: "btn btn-primary" , id: "submit" %>
      <% end %>
  </div>
</div>
<%= render "shared/footer" %>
```
コントローラーの`redirect_to`を削除する
```comments_controller.rb
 class CommentsController < ApplicationController
before_action :authenticate_user!, only: [:create, :destroy]
  def create
    @recipe = Recipe.find(params[:recipe_id])
    @comments = @recipe.comments.includes(:user)
    @comment = Comment.new(comment_params)
    @comment.user_id = current_user.id
    @comment.recipe_id = @recipe.id
-----ここから削除-----
    if @comment.save
      redirect_to recipe_path(@recipe)
    end
-----ここから削除-----
    @comment.save
  end

  def destroy
    @recipe = Recipe.find(params[:recipe_id])
    @recipe_comments = @recipe.comments
    if Comment.find_by(id: params[:id], recipe_id: params[:recipe_id]).destroy
      redirect_to recipe_path(@recipe)
    end
  end

  private
  def comment_params
    params.require(:comment).permit(:content)
  end
end
```
`views/comments`の配下に`create.js.erb`を作成
```create.js
$("#comment_content").val("");
$('#comments').html("<%= escape_javascript(render 'comments', comment: @comment) %>")
```
ここまでで、コメント投稿機能が非同期通信で行えるようになりました。
# ④削除機能の非同期化
`link_to`では`remote: true`と指定することで`destroy.js.erb`を探しにいくようにします。
```comments.html
 <% @comments.each do |comment| %>
  <div class="comments_list">
    <div class="comments-title">
      <%= comment.user.user_name %>さんの投稿
    </div>
    <div class="comments-items">
      <div class="content">
        <%= comment.content %>
      </div>
      <div class="comment-delete">
      <% if current_user == comment.user %>
        <%=link_to "削除", recipe_comment_path(comment.recipe,comment), method: :delete, remote: true, class:"comment-delete" %>///deleteの後にremoteを追加
      <% end %>
      </div>
    </div>
  </div>
<% end %>

```
destroyアクションのredirect も削除する。
```comments_controller.rb
 class CommentsController < ApplicationController
before_action :authenticate_user!, only: [:create, :destroy]
  def create
    @recipe = Recipe.find(params[:recipe_id])
    @comments = @recipe.comments.includes(:user)
    @comment = Comment.new(comment_params)
    @comment.user_id = current_user.id
    @comment.recipe_id = @recipe.id
    @comment.save
  end

  def destroy
    @recipe = Recipe.find(params[:recipe_id])
    @recipe_comments = @recipe.comments
-----ここから下削除-----
    if Comment.find_by(id: params[:id], recipe_id: params[:recipe_id]).destroy
      redirect_to recipe_path(@recipe)
    end
-----ここまで削除-----
-----ここから追記-----
    @comments = @recipe.comments.includes(:user)
    Comment.find_by(id: params[:id], recipe_id: params[:recipe_id]).destroy
-----ここまで追記-----
  end

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

```
`views/commnets`の配下に`destroy.js.erb`を作成します。
```destroy.js
+$('#comments').html("<%= escape_javascript(render 'comments', comment: @comment) %>")
```
ここまで削除機能も非同期で行えるようになります。
中々、自分だけでは解決できず、Qiitaの記事を参考にさせていただきながら、質問もさせていただきながら実装することができました。もっと理解を深めるため、より詳しく説明できるようしていきたいと思います。