Railsアプリに非同期通信のいいね機能を実装

# はじめに
Rialsアプリに非同期通信のいいね機能を実装しました。
方針としては、部分テンプレートを作成してレスポンスをJS形式で返すことで画面の一部のみを更新するというものです。備忘録としてまとめたいと思います。

# 実装イメージ
![ezgif.com-gif-maker (9).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/772095/8514b708-6636-6068-3c21-e4698d60c852.gif)

* **非同期でいいねといいねの解除ができる**
* **いいねされたらカウントに反映される**

# 開発環境

* macOS Catalina
* Ruby 2.6.5
* Ruby on Rails 5.2

# 前提

* userとpost(投稿)モデルを作成済み
* fontawesomeを導入済み
* jQueryを導入済み

# 目次
[1.モデルとアソシエーション](#1-モデルとアソシエーション)
[2.ルーティング](#2-ルーティング)
[3.コントローラー](#3-コントローラー)
[4.view](#4-view)

## 1. モデルとアソシエーション
モデルとアソシエーションの構成は以下の通りです。

![スクリーンショット 2021-04-17 0.15.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/772095/b9bc43a1-4fa7-ca74-fc34-8a5e509adefd.png)
ではfavoriteモデルを作成します。

```php:ターミナル
$ rails g model favorite user:references post:references 
$ rails db:migrate
```
次にアソシエーションを組みます。

```user.rb
  has_many :posts, dependent: :destroy
  has_many :favorites, dependent: :destroy

  # すでにいいねしているかを判定するメソッド
  def already_favorited?(post)
    self.favorites.exists?(post_id: post.id)
  end
```
`already_favorited?`メソッドを定義します。
これはもし投稿にいいねしていたら解除のリンクを表示させ、解除していたらいいねリンクを表示させる条件分岐のために記述しています。


```post.rb
  belongs_to :user
  has_many :favorites, dependent: :destroy
```

```favorite.rb
  belongs_to :user
  belongs_to :post
```

## 2. ルーティング
いいねは投稿ひとつひとつに紐付いているのでルーティングはネストさせて記述します。
ネストさせることでアソシエーション先のレコードのidをparamsに追加してコントローラーに渡せるようになります。
（今回の場合はfavoriteのidを取得できる）

```routes.rb
 resources :posts, only: [:index, :new, :create, :show, :destroy] do
    resource :favorites, only: [:create, :destroy]
 end
```

## 3. コントローラー
favorites_controller.rbではcreateとdestroyアクションを定義します。

```php:favorites_controller.rb
class FavoritesController < ApplicationController
  before_action :set_post

  def create
    @favorite = Favorite.create(user_id: current_user.id, post_id: @post.id)
    @favorite.save
  end

  def destroy
    @favorite = Favorite.find_by(user_id: current_user.id, post_id: @post.id)
    @favorite.destroy
  end

  private
  def set_post
    @post = Post.find(params[:post_id])
  end

end
```
**set_post**
・before_actionを設定することで、アクション実行前にどの投稿に対するものなのかを判断するためにidを取得します。

```posts_controller.rb
  def show
    @post= Post.find(params[:id])
  end
```

## 4. view
非同期通信をするために`_favorite.html.erb`という部分テンプレートを作成します。
更にcreateとdestroyアクション実行時にページの一部を更新するために`create.js.erb`と`destroy.js.erb`というファイルを作成します。
JS形式でレスポンスするために`js.erb`という拡張子になっています。
ディレクトリ構成は以下の通りです。

```php:
views
|-posts
|   |-show.html.erb
|-favorites
    |-_favorite.html.erb
    |-create.js.erb
    |-destroy.js.erb
```
まずは、投稿詳細ページのviewから記述します。

```php:posts/show.html.erb
<div id="favorite_area_<%= post.id %>", class="favoriteArea">
  <%= render partial: "favorites/favorite", locals: { post: @post } %>
</div>
```
divタグに`favorite_area_<%= post.id %>`というidを付与しています。
これはどの投稿に対するものなのかを判別するために記述しています。

またrenderメソッドで`_favorite.html.erb`ファイルを呼び出しています。
localsオプションではpostコントローラーで定義した`@post`の変数を部分テンプレートのなかで`post`として使用できるように定義しています。

次に部分テンプレートです。

```php:favorites/_favorite.html.erb
<% if current_user.already_favorited?(post) %>
  <%= link_to post_favorites_path(post), method: :delete, class: "goodLink", remote: true do %>
    <i class="fas fa-heart"></i>
  <% end %>
<% else %>
  <%= link_to post_favorites_path(post), method: :post, class: "goodLink", remote: true do %>
    <i class="far fa-heart"></i>
  <% end %>
<% end %>
<p class="favoriteCount"><%= post.favorites.count %></p>
```
`user.rb`で定義した`already_favorited?`メソッドを使用しています。
link_toには`remote: ture`を記述することで非同期通信をしますという意味になります。これでHTML形式ではな先程作成した`js.erb`ファイルを返す挙動になります。
最後の行の`<%= post.favorites.count %>`はcountメソッドを使用していいねされている数を表示しています。

最後にjs.erbファイルの編集です。

```favorites/create.js.erb
<!-- #favorite_area_<%= @post.id %>この部分のHTMLだけ、renderで部分的に更新するという処理 -->
$("#favorite_area_<%= @post.id %>").html("<%= j(render partial: 'favorites/favorite', locals: { post: @post }) %>");
```

```favorites/destroy.js.erb
<!-- #favorite_area_<%= @post.id %>この部分のHTMLだけ、renderで部分的に更新するという処理 -->
$("#favorite_area_<%= @post.id %>").html("<%= j(render partial: 'favorites/favorite', locals: { post: @post }) %>");
```
意味としては`favorite_area_<%= @post.id %>`というidを付与したdivタグの中身を`_favorite.html.erb`の内容で更新するという内容になります。

以上で非同期のいいね機能を実装できていると思います!