非同期通信でいいね機能実装

こちらの記事が大変参考になりました。↓
https://note.com/become_engineer/n/n45f7285622e3

https://toshpit.com/rails-goodness-part1/

備忘録としてメモしておきます。
# 非同期通信とは
ブラウザのリロード更新無しでもレスポンスが得られるというものです。（例えばツイッターやインスタグラムなどの、投稿に対して「いいね」を押すことできる）その際に、どこか別のページに遷移されることがないことや、ページがリロードされることなく、そのページに留まったままでいいねが反映される子を見ることが出来ます。
「Ajax」というJava Scriptによる非同期通信の手法で「いいね」機能を実装して行きます。

# 前提
UserモデルとCommentモデルが存在し、投稿をDBに保存できて、ビューに反映されている

# 実装
likeモデルの作成
```
rails g model Like user_id:integer post_id:integer
rails db:migrate
```

各テーブルに対してアソシエーションを記述します。

```app/models/user.rb
class User < ApplicationRecord

（省略）
   has_many :comments, dependent: :destroy
   has_many :likes, dependent: :destroy
   has_many :liked_comments, through: :likes, source:　:comment

def already_liked?(comment)
    self.likes.exists?(comment_id: comment)
  end
end
```

```app/models/post.rb

class Post < ApplicationRecord
 belongs_to :user
 has_many :likes, dependent: :destroy
  has_many :liked_users, through: :likes, source: :user
end
```

```app/models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :comment

  validates :user_id, presence: true
  validates :comment_id, presence: true

  counter_culture :comment
end
```

いいねをカウントするためにcounter_cultureというgemを使用しています。

※counter_cultureの導入

```.Gemfile
# いいねcounter
gem 'counter_culture'
```

```
bundle install
```


**そもそもcounter_cultureってなに？**
Ruby on Railsには、counter_cultureという親レコードが持ってる子レコードの数を測定してカラムに吐き出してくれる便利な機能があります。

counter_cultureができること
・条件に一致した子レコード数を集計
・途中からカウンターを追加して、現在の値を集計


そして次はコントローラーを作成して、メソッドを書いて行きます。

```
rails g controller likes
```

```likes_controller.rb
class LikesController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]
  before_action :comment_params
    def create
      Like.create(user_id: current_user.id, comment_id: params[:id])
    end
  
    def destroy
      Like.find_by(user_id: current_user.id, comment_id: params[:id]).destroy
    end
  
    private
  
    def comment_params
      @comment = Comment.find(params[:id])
    end
  end
```

投稿のidを取得するためbefore_actionでpost_paramsというメソッドを実行させます。ビューに定義した@postというインスタンス変数を使用して、非同期通信でビューにデータを反映させるためです。

そして最後にルーティングを記述します。

```routes.rb

resources :comments, only: [:create, :destroy] do
    resources :likes, only: [:create, :destroy]
  end

post 'like/:id', to: 'likes#create', as: 'create_like'
  delete 'like/:id', to: 'likes#destroy', as: 'destroy_like'
```

Comment に対する”いいね”なので、comments の配下としてパスを設定します。
こうすることで、どの Comment に対する”いいね”なのか id によって判別がつくようになります。

asオプションを使用することで上記画像のようにPrefixのpathの名前を任意のものに指定することが出来ます。
ここまでで、非同期通信(Ajax)でのいいね機能の実装準備が出来ました。ここからはAjaxで実装を進めて行きます。Java ScriptでHTMLを書き換えたい部分を、部分テンプレートとして切り出します。
例として_comment.html.erbを以下のように編集します。投稿の表示を記述してある部分に以下を追加します。


![1d6dc499af8e3ec1efab1d7b2fca0c2b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1962714/8f169920-7e6b-0a2e-84e2-bc02c0df46a0.png)



```comments/_comment.html.erb
<li id="comment-<%= comment.id %>" data-comment-id="<%= comment.id %>">
<span class="user"><%= link_to comment.user.name, comment.user %></span>
<span class="content"><%= comment.content %></span>
# 上記のcommentが使用できる範囲内での任意の位置に以下を記述
<div id="likes_buttons_<%= comment.id %>">
  <%= render "likes/like", comment: comment %>
 </div>
</li>
```

idを付与することにより、どの投稿に対していいねが押されたのかを判別し、HTMLを切り替えられるようにします。
そして部分テンプレートを記述します。likesディレクトリに_like.html.erbを作成し以下のように編集します。

```likes/_like.html
<% if user_signed_in? && !current_user?(comment.user) %>
  <span class="like">
    <% if current_user.already_liked?(comment) %>
      <td>
      <%= link_to destroy_like_path(comment), method: :DELETE, remote: true do %> 
  <i class="fa fa-heart unlike-btn"></i> 
<% end %> 
<%= comment.likes.count %>
      </td>
    <% else %>
      <td>
      <%= link_to create_like_path(comment), method: :POST, remote: true do %> 
      <i class="fa fa-heart like-btn"></i> 
    <% end %> 
    <%= comment.likes.count %>
      </td>
    <% end %>
  </span>
<% end %>   
```

current_user.already_liked?こちらでいいねの有無を判別しています。そして、 「post.likes.count」でいいね数を表示しています。またそれぞれに「remote: true」を付与することによってレスポンスの形式をJava Scriptに変更することが出来ます。これによりそれぞれレスポンスとして返却されるビューファイルが「create.js.erb」,「destroy.js.erb」へと変更されます。それではそれぞれのファイルを作成し、非同期通信が出来ているかアラートを表示させて確認します。以下のように編集します。

```app/views/likes/create.js.erb
alert('いいねが出来ている');
```

```app/views/likes/destroy.js.erb
alert('いいねを解除している');
```


結果、いいねを押したときにアラートの表示が出来ていれば成功です。

最後にFont Awesomeを用いてアイコン装飾をします。

```app/views/likes/create.js.erb
$("#comment-<%= @comment.id %> .like").html("<%= escape_javascript(render "likes/like", comment: @comment) %>");
```

```app/views/likes/destroy.js.erb
$("#comment-<%= @comment.id %> .like").html("<%= escape_javascript(render "likes/like", comment: @comment) %>");
```



```application.html
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/all.css" integrity="sha384-Bfad6CLCknfcloXFOyFnlgtENryhrpZCe29RTifKEixXQZ38WheV+i/6YWSzkz3V" crossorigin="anonymous">
```

```_like.html
<% if user_signed_in? && !current_user?(comment.user) %>
  <span class="like">
    <% if current_user.already_liked?(comment) %>
      <td>
      <%= link_to destroy_like_path(comment), method: :DELETE, remote: true do %> 
  <i class="fa fa-heart unlike-btn"></i> 
<% end %> 
<%= comment.likes.count %>
      </td>
    <% else %>
      <td>
      <%= link_to create_like_path(comment), method: :POST, remote: true do %> 
      <i class="fa fa-heart like-btn"></i> 
    <% end %> 
    <%= comment.likes.count %>
      </td>
    <% end %>
  </span>
<% end %>   
```