いいね機能を非同期通信（Ajax）で実装

#開発環境

> ruby 2.6.3
> Rails 5.2.6

#対象となる人

- Twitterのようないいね機能を取り入れたい
- すでにいいね機能を実装済みで非同期通信にしたい
- ユーザー、サーバーのことを考えたアプリにしていきたい

#前提

すでにいいね機能は実装済みで、非同期通信化をこの記事では説明していきます。
jqueryが入っている（jqueryが入っていないと非同期で通信ができません）
モデル名、変数名等は適宜、ご自身の開発環境に変換してお考えください。

user  ユーザー
post  投稿
like  いいね
で進めていきます。

#大事な３つのポイント
この３つだけ押さえていれば非同期通信はかんたんに実装できます！

- link_toをremote: trueにしてjs形式で送信
- 部分テンプレートをつくる
- jsファイルを用意する

ではいってみましょう〜

#link_toをjs対応にする

link_toはデフォルトでhtml形式で送信されますが、今回は非同期通信をしたいので、js形式で送信します。
方法はいいね機能を実装しているlink_toに`remote: true`を追加していきます。

```erb:views/posts/show.html.erb
:
:
<!--いいね機能-->
  <% if @post.liked_by?(current_user) %>
    <p>
      <!--remote: trueを追加-->
      <%= link_to post_likes_path(@post), method: :delete, remote: true do %>
        <i class="fas fa-heart"></i><%= @post.likes.count %> いいね
      <% end %>
    </p>
    <% else %>
    <p>
　　　　　　　　　　　　<!--remote: trueを追加-->
      <%= link_to post_likes_path(@post), method: :post, remote: true do %>
        <i class="far fa-heart"></i><%= @post.likes.count %> いいね
      <% end %>
    </p>
  <% end %>
:
:
```

remote: trueを追加することで、jsファイルを探しにいってくれます。

#部分テンプレートをつくる

非同期通信ではビュー全体ではなく、指定の部分のみをリロード無しで返してくれます。
なので、いいね機能が動いたときに変更したいビューの箇所を部分テンプレートにしていきます。
部分テンプレート化した場合は変数名に@はつけれないので@を外します。

```erb:views/likes/_like.html.erb
<!--@postをpostに変更-->
<% if post.liked_by?(current_user) %>
    <p>
      <%= link_to post_likes_path(post), method: :delete, remote: true do %>
        <i class="fas fa-heart"></i><%= post.likes.count %> いいね
      <% end %>
    </p>
    <% else %>
    <p>
      <%= link_to post_likes_path(post), method: :post, remote: true do %>
        <i class="far fa-heart"></i><%= post.likes.count %> いいね
      <% end %>
    </p>
  <% end %>
```

部分テンプレート化したらrenderで呼び出します。
その際、renderをpost.idを識別できるクラス名をつけたdivタグで囲みます。

```erb:views/posts/show.html.erb
:
:
<!--いいね機能で部分的に更新したい場所-->
<div class="likes_buttons_<%= @post.id %>">
  <%= render 'likes/like', post: @post %>
</div>
:
:
```

ここの考え方が少し難しいかも知れません。
もしclass名をlikes_buttonsにするとどうなるか考えてみましょう
likes_buttonsにすると全ての投稿のいいねボタンが同じclass名になってしまいます。
そうすると、いいねボタンを押したときに全ての投稿のいいね部分が呼び出されて、いいねした投稿以外の投稿も全ていいね済みの状態になったり、いいね削除の状態になったりします。
そんな挙動だれも望んでないですよね。

そこで、class名を`likes_buttons_<%= @post.id %>`とすることで、どの投稿にいいねをしたか判別できるようにしています。
likes_buttons_1
likes_buttons_2
のようになるので、どの投稿にいいねしたかがわかります。

#jsファイルを用意する

link_toをremote: trueにしたことでjsファイルを探しにいくので、対応するjsファイルを作っていきます。
いいね機能はlikesコントローラーのcreateとdestroyアクションでいいねをしたり削除したりするので対応したjsファイル名にする必要があります。

`views/likes`直下に`create.js.erb`と`destroy.js.erb`を作成します。

```erb:views/likes/create.js.erb
$('.likes_buttons_<%= @post.id %>').html("<%= j(render 'likes/like', post: @post) %>");
```

```erb:views/likes/destroy.js.erb
$('.likes_buttons_<%= @post.id %>').html("<%= j(render 'likes/like', post: @post) %>");
```

変更したい場所のclass名を指定して、その中のビューを非同期で更新しています。

#redirect_toを削除

ここまででいいね機能の非同期通信はできているのですが、まだリダイレクト処理が残っているので、最後にlikesコントローラーのredirect_toを削除します。

```controllers/likes_controller.rb
class LikesController < ApplicationController

  def create
    @post = Post.find(params[:post_id])
    @like = current_user.likes.new(post_id: @post.id)
    @like.save
    redirect_to post_path(@post)  #この行を削除
  end

  def destroy
    @post = Post.find(params[:post_id])
    @like = current_user.likes.find_by(post_id: @post.id)
    @like.destroy
    redirect_to post_path(@post)  #この行を削除
  end

end
```

これでいいね機能の非同期通信は完成です！

#まとめ

大事なポイント3つさえ押さえていれば、非同期化できるのでもう一度おさらいです。
> - link_toをremote: trueにしてjs形式で送信
> - 部分テンプレートをつくる
> - jsファイルを用意する

非同期化することでユーザーもいいねしたときに待つ時間がなくなるし、サーバーもビューごとリダイレクトしなくて済むので、負担も軽減されます。
今や、ほとんどのWebサービスでいいね機能の非同期化は当たり前になっているので、最低限できるようにしておきましょう！