【Rails】いいね機能を非同期で実装する手順（jQuery, liked_by?メソッド, FontAwesome）

# 概要
ユーザーが任意のitemに対していいね！（非同期通信）ができるように実装したので、その手順をここに残しておく。

# 前提
* deviseによるログイン機能が実装できている。
* いいね！機能は、非同期通信で行う。liked_by?とJQueryで実践する。
* index(トップページ)に表示されているitemのいいね！ボタンをクリックできる。
* 一人のユーザーは、一つのitemに対して、いいね！を1回までしか押せない。
* 自分がすでにいいね！済みならそれを消すことができる。
* いいね！したitem一覧はユーザーのマイページから一覧で見ることができる。
* テーブルやカラムの関連性は以下データベースと後述のアソシエーションを参照。

# データベース設計
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/0760af3f-3d91-2fa5-e8a3-d0c1ce7c344d.png)


# jQuery導入

* JQuery導入：`yarn add jquery`を実行
    * Rails5以前の導入方法ではjquery-railsというGemをインストールするのが基本のよう
    * 今回はWebpackerで管理するのでyarnコマンドを使用してインストール
    * エラー`Uncaught ReferenceError: $ is not defined`は実装失敗に出る

* Webpackの設定を行う為、`environment.js`に以下を追加
```config/webpack/environment.js
const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)
```

* application.jsでjQueryを呼び出せるようにするため、`application.js`に以下を追加
```app/javascript/packs/application.js
require("jquery") 
```

# それぞれのモデルのアソシエーションを設定
* アソシエーションの関係性は以下の通り
    * UsersとLikesは一体多の関係性（Userは複数のいいね！をできるため）
    * ItemsとLikesも一体多の関係性（Itemは複数のいいね！を持つため）
* user.rbに以下を記載
    * liked_by?メソッドを作成。whereメソッドを使用し、likesテーブルに`item_id`が存在しているかどうか検索をかけ、存在していなければいいね！・存在していればいいね！を解除するという条件分岐のメソッド。

```app/models/user.rb
has_many :reviews, dependent: :destroy
has_many :likes, dependent: :destroy

def liked_by?(item_id)
  likes.where(item_id: item_id).exists?
end
```

* likeモデルに以下を記載
```app/models/like.rb 
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :item
end
```

* itemモデルに以下を記載
```app/models/item.rb
class Item < ApplicationRecord
  has_many :reviews, dependent: :destroy
  has_many :likes, dependent: :destroy
end
```

* migrateでlikeを追加

```db/migrate/20220516123542_create_likes.rb 
class CreateLikes < ActiveRecord::Migration[6.0]
  def change
    create_table :likes do |t|
      t.integer :user_id, null: false
      t.integer :item_id, null: false
      t.timestamps
    end
  end
end
```


# ルーティングを設定
* いいね！をつけるcreateアクションをpostメソッド、いいね！を外すdestroyアクションをdeleteメソッドを使ってルーティングを記述する
* オプションでas:を記述することでPrefixの名前を指定できる

```config/routes.rb 
 post 'like/:id' => 'likes#create', as: 'create_like'
 delete 'like/:id' => 'likes#destroy', as: 'destroy_like'

```

* idが必要なため、memberを使用。以下を追記
```config/routes.rb
resources :users do
    member do
      get :likes
    end
  end

```

# controllerを設定（いいね！をcreate・destroyする設定を行う）
* `likes_controller`を作成、以下を記載。
    * itemのidを取得するため`before_action`で`item_params`というメソッドを実行させる。ビューに定義する`@item`というインスタンス変数を使用し、非同期通信でビューにデータを反映させるため。
  
```app/controllers/likes_controller.rb
class LikesController < ApplicationController
  before_action :item_params
  def create
    Like.create(user_id: current_user.id, item_id: params[:id])
  end

  def destroy
    Like.find_by(user_id: current_user.id, item_id: params[:id]).destroy
  end

  private

  def item_params
    @item = Item.find(params[:id])
  end
end
```

* `users_controller`にて、いいねした投稿を探し、`@like_items`に格納。これはマイページのいいね！一覧で利用する（後述）
    * pluckメソッドは、特定のカラムの値だけ取得したい場合に使える
```app/controllers/users_controller.rb
 def likes
    @user = User.find(params[:id])
    likes = Like.where(user_id: @user.id).pluck(:item_id)
    @like_items = Item.find(likes)
  end
```

# ビューを編集
* いいね！をつける場合・いいね！を消す場合の方法は以下の通り
    * ユーザーがサインインしていれば、いいねを押すことが出来るようにするので、`<% if user_signed_in? %>`と記述する
    *  `<% if current_user.liked_by?(item.id) %>`でliked_by?メソッドを呼び出し、いいねの有無を判別
    * 今回は部分テンプレートでlike.htmlを作っているのでそこに記載しているが、index.htmlでも問題ない

```erb:app/views/likes/_like.html.erb
<% if user_signed_in? %>
 <% if current_user.liked_by?(item.id) %>
   <td>
    <%# いいね！を消す場合 %>
   <%= link_to destroy_like_path(item), method: :DELETE, remote: true do %>
   <i class="fa fa-heart unlike-btn">お気に入り！</i>
     <% end %>
      </td>
 <% else %>
   <td>
    <%# いいね！をつける場合 %>
   <%= link_to create_like_path(item), method: :POST, remote: true do %>
      <i class="fa fa-heart like-btn">お気に入り！</i>
         <% end %> 
    </td>
 <% end %>
<% end %>
```

* 部分テンプレートを利用する場合は、index.htmlには以下のように記載する。idを付与することにより、どのitemに対していいねが押されたのかを判別し、HTMLを切り替えられるようにする。
```html
<div id="likes_buttons_<%= item.id %>">
 <%= render partial: 'likes/like', locals: {item: item} %>
</div>
```

* 非同期いいねでは、link_toのオプションに`remote: true`が必須。これにより、js.erbファイルを呼び出し、ページ遷移を行わない非同期通信が可能になる。ちなみに作成したjsファイルの中身とディレクトリは以下
    * コントローラーで指定したアクションと同じ名前で.js.erbファイルを作ること

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

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


* ユーザーのマイページに「いいね！したitem一覧を見る」ボタンを設置。CSSは任意。
```erb:app/views/users/show.html.erb
<a class="btn btn--orange btn--radius" href= <%= likes_user_path(@user) %>>いいね！した一覧を見る</a>
```

* なお、Railsでは、一般的にlink_toメソッドを使用するので、以下のようにすることが多いかも
```erb:app/views/users/show.html.erb
<%= link_to "いいね！した一覧を見る", likes_user_path(@user), class: “btn btn--orange btn--radius” %>
```
* 「いいね！したitem一覧」を表示させる。`users_controller.rb`で設定した`@like_items`の中身をeach文により表示させる。
```erb:app/views/users/likes.html.erb
<div class="box-title"> いいね！したitem一覧</div>
 <% @like_items.each do |item|%>
  <ul>
   <a class="btn btn-radius-solid2" href= <%= item_path(item.id) %>><%= item.item_name %><i class="fas fa-angle-right fa-position-right"></i></a>
  </ul>
 <% end %>
</div>
```

# FontAwesomeでハートマークにする
* 以下を`application.html.erb`のheadタグ内に追記
```erb:app/views/layouts/application.html.erb
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/all.css" integrity="sha384-Bfad6CLCknfcloXFOyFnlgtENryhrpZCe29RTifKEixXQZ38WheV+i/6YWSzkz3V" crossorigin="anonymous">
 </head>
```

* ビューで以下のように記載

```erb:app/views/likes/_like.html.erb
<i class="fa fa-heart like-btn">お気に入り！</i>
<i class="fa fa-heart unlike-btn">お気に入り！</i>
# ほか省略
```

* CSSは以下で設定可能

```app/assets/stylesheets/likes.scss
.like-btn {
  font-size: 15px;
  color: #808080;
 }
 
 .unlike-btn {
  font-size: 15px;
  color: #e54747;
 }
```

# ソースコードはこちら
* [Ryo-0131/Republic_of_Citrus](https://github.com/Ryo-0131/Republic_of_Citrus)

# 参考記事
* [いいね機能を非同期で実装【Rails】【jQuery】](https://qiita.com/fumikao/items/373caa60b77f27f2dbdd)
* [Ajax(非同期通信)で「いいね」機能を実装する(Ruby on Rails)](https://note.com/become_engineer/n/n45f7285622e3)
* [【Rails】いいね機能の実装（非同期）](https://qiita.com/oak1331/items/3e5508d9405c96ae6786)

# erbファイルのマークダウンについて
* まったく関係ない話ですが、qiitaでerbファイルのソースコードをマークダウン表記する場合、ファイル名を`erb:index.html.erb`としなければハイライトされません。.erbだけで適応されると良いのですが(-_-;)