いいね機能を非同期で実装【Rails】【jQuery】

## この記事の内容
![like.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427185/5e2db2ae-49b7-fa63-e68f-3a21beb48149.gif)
TODOリストを共有できるアプリを作っていて、いいね機能を非同期にて実装しました。
すでにたくさんのQiita記事がありますが、つまったポイントもあったので、自分なりにまとめ直してみます。
（コンセプトは「人生でやりたいこと100のリストの共有」なので、todoをdreamという言葉を使って表現しています。）

## 前提
Rails 5.2.3

## 構成
userの詳細ページにdreamリストが表示されています。
viewの構成としては、
`/views/users/show.html.erb`内で同じ階層の`_dream.html.erb`が部分テンプレートとして呼ばれ、userのdreamを繰り返し表示しています。

CSSフレームワークはMaterializeを使用しています。

アソシエーション：
users - has_many :dreams, has_many :likes
dreams - belongs_to :user, has_many :likes
likes - belongs_to :user, belongs_to :dream

## 流れ
1. jQueryの準備
2. いいねボタンを作成
3. コントローラ記述
4. remote: trueにてjs.erbファイルを呼び出し
5. js.erbファイル作成

## 実行

### 1. jQueryの準備
非同期化するにあたり、Rails内でjQueryを使えるように準備します。
まずはgemの導入です。

```:Gemfile
gem 'jquery-rails'
```
ターミナルで　`bundle install`します。
そしてapplication.jsに記述を追加します。

```js:app/assets/javascripts/application.js
//= require jquery3
//= require rails-ujs
//= require_tree .
```
順番が重要です。jqueryを最初に読み込む必要があります。

### 2. いいねボタンを作成
後から使い回ししやすいように、部分テンプレート化しています。

`_dream.html.erb`ではテーブルでtodoリストを表示しているので、いいねの項目がtd内に入っています。
`id`の記載については5の項目で説明します。

```erb:app/views/users/_dream.html.erb
# いいね機能該当部分
<td id="like-<%= dream.id %>">
  <%= render partial: "like", locals: { dream: dream } %>
</td>
```

`_like.html.erb`ではすでにいいねがあるかないかで★か☆かを出し分けて（Materializeのアイコンを使用しています）、最後にいいね数を`dream.likes.length`で表示しています。

```erb:app/views/users/_like.html.erb
<% if Like.find_by(user_id: current_user.id, dream_id: dream.id) %>
  <%= link_to "/dreams/#{dream.id}/likes", method: :delete %>
    <i class="material-icons">star</i>
  <% end %>
<% else %>
  <%= link_to "/dreams/#{dream.id}/likes", method: :post %>
    <i class="material-icons">star_border</i>
    <% end %>
<% end %>
<%= dream.likes.length %>
```

### 3. コントローラ記述
いいねのcreateとdestroyを定義していきます。

```ruby:app/controllers/likes_controller.rb
class LikesController < ApplicationController
  before_action :set_dream

  def create
    @like = Like.create(user_id: current_user.id, dream_id: @dream.id)
  end
  
  def destroy
    @like = Like.find_by(user_id: current_user.id, dream_id: @dream.id)
    @like.destroy
  end

  private
  def set_dream
    @dream = Dream.find(params[:dream_id])
  end
end
```
ルーティングも忘れずに。

```ruby:config/routes.rb
post '/dreams/:dream_id/likes' => "likes#create"
delete '/dreams/:dream_id/likes' => "likes#destroy"
```
この時点で、非同期ではないですがいいね機能が実装できているはず。

### 4. remote: trueにてjs.erbファイルを呼び出し
いいねボタンの`link_to`に`remote: true`を追加します。

```erb:app/views/users/_like.html.erb
<% if Like.find_by(user_id: current_user.id, dream_id: dream.id) %>
  <%= link_to "/dreams/#{dream.id}/likes", method: :delete, remote: true do %>
    <i class="material-icons">star</i>
  <% end %>
<% else %>
  <%= link_to "/dreams/#{dream.id}/likes", method: :post, remote: true do %>
    <i class="material-icons">star_border</i>
    <% end %>
<% end %>
<%= dream.likes.length %>
```
この記述により、通常であれば、`link_to`で呼ばれるアクションに対応するhtml.erbファイルを呼び出すところ、js.erbファイルを呼び出せるようになります。
なのでページ遷移を行わず非同期で通信が行われるようになります。

### 5. js.erbファイル作成
js.erbファイルはその名前の通り、javascriptのファイルでありながら、ERBタグを使うことでrubyのコードを書ける優れものです。
コントローラーで定義したインスタンスを`<% %>`、`<%= %>`を使うことでそのままjsファイルに記述できます。

まずはcreateとdestroyそれぞれのアクションに対応するjs.erbファイルを作成します。
`app/views/likes/create.js.erb`
`app/views/likes/destroy.js.erb`

ここで2.で記述していた`id`について説明します。

```erb:app/views/users/_like.html.erb(再掲)
<td id="like-<%= dream.id %>">
  <%= render partial: "like", locals: { dream: dream } %>
</td>
```
jsでイベントを発火させるためには、idまたはclassでセレクタを指定しますが、今回はどのdreamに対するいいねなのかを判別するために、id内にそのdreamのidを含める必要があります。
上のように書くことによって、`id="like-1"`のようなidを指定することができます。

ここまで来たらあとはjs.erbファイルの記述だけです。

```erb:app/views/likes/create.js.erb
$("#like-<%= @dream.id %>").html("<%= j(render partial: 'users/like', locals: { dream: @dream }) %>");
```

```erb:app/views/likes/destroy.js.erb
$("#like-<%= @dream.id %>").html("<%= j(render partial: 'users/like', locals: { dream: @dream }) %>");
```
これだけです。

まずはセレクタの指定ですが、js.erbファイルなのでコントローラで定義した変数が使えます。
`id="like-<%= dream.id %>"`に対応するように指定します。

そして、jQueryのhtml()メソッドで、指定したセレクタのhtmlを置き換えます。

その置き換える内容が
`"<%= j(render partial: 'users/like', locals: { dream: @dream }) %>"`の部分です。
部分テンプレートの`_like.html.erb`を呼び出しています。
（renderの前にある`j`は、`escape_javascript`のエイリアスで、改行と括弧をエスケープしてくれるメソッドです。）

これにより、likeが更新された状態で、ifで条件分岐されたりlikes.countが表示されたりします。
↓
**非同期処理の実現！**

一度流れをつかむことができれば応用が効きそうなので、今後もいろいろなところで使ってみようと思います。

##### 参考
[Railsで remote: true と js.erbを使って簡単にAjax(非同期通信)を実装しよう！(いいね機能のデモ付)](https://qiita.com/__tambo__/items/45211df065e0c037d032)

---
分かりにくい点・間違っている点などがありましたらご指摘いただきますよう、よろしくお願いいたします。