[Rails]Ajaxを用いて非同期でコメント機能の実装

## 実装すること
Ajaxを用いて非同期通信を行い、下記のコードを書いていきます。
①コメントの作成を非同期で行う。
②コメントの件数もコメント作成と同時に非同期で更新されるようにする。
③3件目以上のコメントは隠して「もっと見る」を押すと見れるようにする。
④コメントの削除も非同期で行えるようにする。

## 完成形
[![Image from Gyazo](https://i.gyazo.com/ca5163a1fe88e86391555c557952d404.gif)](https://gyazo.com/ca5163a1fe88e86391555c557952d404)
## Ajaxとは
✔︎同期通信
>クライアントとサーバーが交互に処理を行い、同調して通信を行うこと。
→webブラウザがリクエストを送り、webサーバーが作成したHTMLファイルをレスポンスとして返し、webブラウザがそれを受け取って表示することでコンテンツの内容を変化させている。
>>・欠点
HTMLファイルを受け取ってから表示の処理を行うため、全体としてページの更新に時間がかかってしまう。
また、送信するデータも多くなりがちで、サーバーに負担がかかってしまう。

✔︎Ajax(非同期通信)
>Webブラウザ上でJavascriptが直接Webサーバーと通信を行い、取得したデータを用いて表示するHTMLを更新する。データのやりとりにはXMLが用いられ、JavascriptはDOMを使ってXMLやHTMLを操作する。HTMLそのものをやりとりするのではなく、更新に必要なデータのみやりとりするため、送信するデータの量は同期通信の時よりも少なくなり、サーバーへの負担が抑えられる。

参考文献:「この1冊で全部わかる　Web技術の基本」

## ER図
User:Item = 1:N
Item:Comment = 1:N
User:Comment = 1:N
CommentテーブルがItemとUserの中間テーブルになります。
<img width="737" alt="コメントER図.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539971/d8ec299b-86a2-8e13-6e1a-3794561c3183.png">

## ページ設計
投稿詳細ページ(item/show.html.erb)でコメントができる。

## モデルの作成
Userモデル、Itemモデルは作成した前提で進めていきます。
作成手順は下記リンク先で説明しております。
[非同期投稿と非同期いいねの実装]https://qiita.com/yuto_1014/items/78d8b52d33a12ec33448
それではCommentモデルを作成していきます。

```
$ rails g model Comment content:text user_id:integer item_id:integer
$ rails db:migrate
```

## アソシエーションの確認
### Userモデル
`dependent: :destroy`は、userが消えればitemもcommentも消えるようにするためです。

```app/models/user.rb
class User < ApplicationRecord
 
 has_many :items, dependent: :destroy
 has_many :comments, dependent: :destroy

end
```

### Itemモデル

```app/models/item.rb
class Item < ApplicationRecord

  belongs_to :user
  has_many :comments, dependent: :destroy

end
```

### Commentモデル
空欄で送信できないようバリデーションを掛けています。

```app/models/comment.rb
class Comment < ApplicationRecord

  belongs_to :user
  belongs_to :item
  #バリデーション
  validates :content, presence: true

end
```

## コントローラーの作成

```
$ rails g controller comments
```

## ルーティングの作成
コメントがどの投稿へのものであるかを識別するために、ルーティングのURLに投稿のIDを含めます。(ネストする)
具体的には「/item/10/comment/」といったURLになります。10がitem_idです。

```config/routes.rb
Rails.application.routes.draw do

  resources :users
  resources :items, only: [:index, :show, :new, :create] do
    resources :comments, only: [:create, :destroy]
  end

end
```

## コントローラーの編集
### items_controller.rb
`order(created_at: :desc)`で、コメントを作成順に取ってきています。

```app/controllers/items_controller.rb
class ItemsController < ApplicationController
  
  def show
    @item = Item.find(params[:id])
    @comment = Comment.new
    #新着順で表示
    @comments = @item.comments.order(created_at: :desc)
  end

```


### comments_controller.rb
・`build`を使うことで、@itemのidをitem_idに含んだ形でcommentインスタンスを作成します。
・保存がされると、`render :index`によって「app/views/comments/index.js.erb」を探しにいきます。
・`form_with`でフォームを送信した時は、デフォルトでjsファイルを探しにいく設定になっています。
htmlファイルを探しにいってほしい場合は、form_withの後に`local: true`と記載する必要があります。
・`form_for`でフォームを送信し、jsファイルを探しに行って欲しい場合は`remote: true`と記載する必要があります。

```app/controllers/comments_controller.rb

  class CommentsController < ApplicationController
	def create
      @item = Item.find(params[:item_id])
      #投稿に紐づいたコメントを作成
      @comment = @item.comments.build(comment_params)
      @comment.user_id = current_user.id
      @comment.save
      render :index
    end

    def destroy
      @comment = Comment.find(params[:id])
      @comment.destroy
      render :index
    end

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

```

## ビューの編集
### items/show.html.erb
コメント一覧とコメント入力フォームはそれぞれパーシャルにしています。
`id="comments_area"` をターゲットにこのdiv内をAjaxで書き換えます。

```app/views/items/show.html

<div class="row">
    <ul>
        <li class="comment-create">
            <h3 class="text-left title">レビュー</h3>
        </li>
  	    <li id="comments_area">
            <%= render partial: 'comments/index', locals: { comments: @comments } %>
	    </li>
    </ul>
    <hr>
    <% if user_signed_in? %>
        <div class="comment-create">
	        <h3 class="text-left">レビューを投稿する</h3>
	        <%= render partial: 'comments/form', locals: { comment: @comment, item: @item } %>
	    </div>
    <% end %>
</div>
```

### comments/_index.html.erb
・each文の`comments.first(2)`で最初の2件、`comments.offset(2)`で最初の2件以外を取ってきています。
・ 3件目以上は通常隠して、「もっと見る」を押すことで表示しています。これは Bootstrapの`collapse`を使用しています。
参考：　[Bootatrap4移行ガイド]https://cccabinet.jpn.org/bootstrap4/components/collapse
・コメントの削除は、`remote:true`を付けることによって、コントローラのdestoryアクションから、index.js.erbを探しに行ってます。
また、`(comment.item_id, comment.id)`とすることで、投稿のidとコメントのidを渡しています。

```app/views/comments/_index.html
<!-- コメント内容(２件) ------------------------------------------------------------------>
<%= comments.count %>件コメント
<h6 class="more" data-toggle="collapse" data-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample">もっと見る....</h6>
<% comments.first(2).each do |comment| %>
  <% unless comment.id.nil? %>
  	<li class="comment-container">
		<div class="comment-box">
			<div class="comment-avatar">
				<%= attachment_image_tag comment.user, :profile_image, fallback: "no_image.jpg", class:"comment-image", size: "40x40" %>
			</div>
			<div class="comment-text">
			    <p><%= link_to "@#{comment.user.name}", users_user_path(comment.user.id) %></p>
			    <div class="comment-entry">
  				    <%= comment.content %>
  				    <% if comment.user == current_user %>
			      		<%= link_to item_comment_path(comment.item_id, comment.id), method: :delete, remote: true, class: "comment_destroy" do %>
			      			<i class="fas fa-trash" style="color: black;"></i>
			      		<% end %>
			    	<% end %>
  				</div>
				<span class="comment-date pull-right">
				      <%= comment.created_at.strftime('%Y/%m/%d %H:%M:%S') %>
				</span>
			</div>
		</div>
	</li>
  <% end %>
<% end %>
<!-- コメント内容(3件目以降) ------------------------------------------------------------------>
	<div class="collapse" id="collapseExample">
			<% comments.offset(2).each do |comment| %>
			  <% unless comment.id.nil? %>
			  	<li class="comment-container">
					<div class="comment-box">
						<div class="comment-avatar">
							<%= attachment_image_tag comment.user, :profile_image, fallback: "no_image.jpg", class:"comment-image", size: "40x40" %>
						</div>
						<div class="comment-text">
						    <p><%= link_to "@#{comment.user.name}", users_user_path(comment.user.id) %></p>
						    <div class="comment-entry">
			  				    <%= comment.content %>
			  				    <% if comment.user == current_user %>
						      		<%= link_to item_comment_path(comment.item_id, comment.id), method: :delete, remote: true, class: "comment_destroy" do %>
			      						<i class="fas fa-trash" style="color: black;"></i>
			      					<% end %>
						    	<% end %>
			  				</div>
							<span class="comment-date pull-right">
							      <%= comment.created_at.strftime('%Y/%m/%d %H:%M:%S') %>
							</span>
						</div>
					</div>
				</li>
			  <% end %>
			<% end %>
	</div>

```

### comments/_form.html.erb
form_withで、`modle: [item, comment]`としています。itemとcommentはそれぞれ、投稿のビューで渡しているインスタンス変数です。投稿に紐づいたコメントを生成するため、ここでitem,commentのインスタンスを渡すことが必要になります。


```app/views/comments/_form.html

<!-- コメント入力フォーム ------------------------------------------------------------>
<%= form_with(model: [item, comment], url: item_comments_path(@item) ) do |f| %>
    <%= f.text_area :content, class: "input-mysize" %>
    <%= f.submit "送信", class: "btn btn-outline-dark comment-submit float-right" %>
<% end %>

```

### comments/index.js.erb
・items/show.html.erbの中で`id="comments_area"`の箇所を書き換える処理になります。
`$("#comments_area")`で`id = "comments_area"`をターゲットとし、`render 'index'`で指定しているcomments/_index.html.erbの内容で書き換えています。
・`$("textarea").val('')`でコメント送信後のコメント入力フォームを空にしています。

```app/views/comments/index.js
$("#comments_area").html("<%= j(render 'index', { comments: @comment.item.comments }) %>")
$("textarea").val('')
```

## 最後に
最後までご覧いただきありがとうございます。
初学者ですので間違っていたり、分かりづらい部分もあるかと思います。
何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。
Ajax関連でいいねとフォローの非同期も実装しています。
↓
[Rails]Ajaxを用いて非同期で投稿機能といいね機能の実装
https://qiita.com/yuto_1014/items/78d8b52d33a12ec33448
[Rails]Ajaxを用いて非同期でフォロー機能の実装
https://qiita.com/yuto_1014/items/8d508b84fd0c2316ba01

## 参考
https://ysk-pro.hatenablog.com/entry/2018/02/10/101739