[Rails]非同期のいいね機能実装

ユーザー(user)が出品した商品(item)にいいねできる機能を実装してます。

## Likeモデル、テーブル作成
`rails g model Like`

```like.rb
class Like < ApplicationRecord
end
```

```XXXXXXXXXXX_create_likes.rb
class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.integer     :user_id
      t.integer     :item_id
      t.timestamps
    end
  end
end
```

`rails db:migrate`マイグレーションファイル実行


## モデル
通常はuserとitemは１対多の関係ですが、いいね機能実装時に関してはlikeテーブルが加わるので、多対多の関係になります。


```user.rb
  has_many :likes, dependent: :destroy
  has_many :like_items, through: :likes, source: :item
```
```item.rb
  has_many :likes, dependent: :destroy
  has_many :liking_users, through: :likes, source: :user
```

`dependent: :destroy`はいいねを外した時に、中間likesテーブルにある該当userとitemのレコードを一緒に削除してくれます。

`:like_items`はuserがどのitemをいいねしているのかを取得
`:liking_users `はitemがどのuserによっていいねされているのか取得

`through: :likes`は多対多の関係で中間likeテーブルを経由するための関連付けで記述

`source:`オプションは関連付け元の名前を指定するため記述

```like.rb
class Like < ApplicationRecord
  belongs_to :item, counter_cache: :likes_count
  belongs_to :user
end
```
`counter_cahce: :likes_count`はリレーションされているlikeの数の値をリレーション先のlikes_countというカラムの値に入れるという意味です。なので**likes_countカラムをitemsテーブルに追加しましょう。**

## itemsテーブルにlikes_countカラム追加
`rails g migration AddLikes_countToItems`

```XXXXXXXXXXX_add_likes_count_to_items.rb
class AddLikesCountToItems < ActiveRecord::Migration[5.2]
  def change
    add_column :items, :likes_count, :integer
  end
end
```
`rails db:migrate`マイグレーションファイル実行


## ルーティング設定
```routes.rb
resources :items
  member do
    post   '/like/:item_id' => 'likes#like',   as: 'like'
    delete '/like/:item_id' => 'likes#unlike', as: 'unlike'
  end
```
いいねをつける時→like　外す時→unlike
as:でルーティングに名前を付けれる。この二つはlike_path,unlike_pathとして使えるようになります。


## コントローラー
`rails g contoller likes`

```likes_controller.rb
class LikesController < ApplicationController
  before_action :set_variables

  def like
    like = current_user.likes.new(item_id: @item.id)
    like.save
  end

  def unlike
    like = current_user.likes.find_by(item_id: @item.id)
    like.destroy
  end

  private
  def set_variables
    @item = Item.find(params[:item_id])
    @id_name = "#like-link-#{@item.id}"
  end
end
```

`@id_name`は非同期で使用します。


## ビュー
```items/show.html.haml
  .option
    = render partial: 'likes/like', locals: { item: @item }
```

`render`を使用し、部分テンプレートへ誘導
likesディレクトリに部分テンプレート`_like.html.haml`ファイルを作成

```likes/_like.html.haml
.option__like{:id => "like-link-#{@item.id}"}
  - if current_user.likes.find_by(item_id: item.id)
    = link_to unlike_item_path(@item.id, @item.id), method: :delete, remote: true, class: "option__like-on" do
      .fas.fa-star
      .option__like-on__text いいね！
      .option__like-on__count
        =item.likes.count
  - else
    = link_to  like_item_path(@item.id, @item.id), method: :post, remote: true, class: "option__like-off" do
      .fas.fa-star
      .option__like-off__text いいね！
      .option__like-off__count
        =item.likes.count
```

いいねボタンのビューを記述します。

`{:id => "like-link-#{@item.id}"}`をつけることで@itemのボタンであることを指定します。

`remote: true`をつけることでリンクを押した時、ajaxを発火させます。

`=item.likes.count`でいいねされた数を表示します。

##いいねボタンの非同期化
`like.js.haml`と`unlike.js.haml`ファイル作成

```likes/like.js.haml
$("#{@id_name}").html('#{escape_javascript(render("likes/like", item: @item  ))}');

```
```likes/unlike.js.haml
$("#{@id_name}").html('#{escape_javascript(render("likes/like", item: @item  ))}');

```

コントローラーで定義した`@id_name`を指定し、`escape_javascript`で先ほど作成した_likeファイルを埋め込んでます。

## Sass
```item.show.scss
          .option {
            display: flex;
            justify-content: space-between;
            &__like {
              &-on {
                text-decoration: none;
                padding: 11px 10px;
                border-radius: 40px;
                color: #3CCACE;
                border: 1px solid #ffb340;
                display: flex;
                line-height: 16px;
                .fas.fa-star {
                  padding-right: 5px;
                }
                &__text {
                  padding-right: 5px;
                }
              }
              &-off {
                text-decoration: none;
                padding: 11px 10px;
                border-radius: 40px;
                color: #333;
                border: 1px solid #f5f5f5;
                display: flex;
                line-height: 16px;
                background: #f5f5f5;
                .fas.fa-star {
                  padding-right: 5px;
                }
                &__text {
                  padding-right: 5px;
                }
              }
            }
          }
```

sassの説明は省略します。


## 完成イメージ
[![Image from Gyazo](https://i.gyazo.com/1ffea844d24855b2dc8926cf200dd7c4.gif)](https://gyazo.com/1ffea844d24855b2dc8926cf200dd7c4)

**ユーザーがいいねした商品一覧を表示させたい方は[こちら](https://qiita.com/yummy888/items/22db2f8b79b5be148b69)をご覧ください**


間違えている部分があったらぜひコメントよろしくお願いします！！