【Rails】いいね機能Ajax実装

前提
---
* Rails6
* Devise実装
* fontawesome導入
* jQuery導入
* いいね対象の投稿機能を実装

実装の流れ
---
1. モデル作成・アソシエーション定義
2. ルーティング設定
3. コントローラー実装
4. ビューの修正・追加

完成形
---
![ezgif.com-gif-maker (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602337/c80a0936-f019-5113-dc12-f3cb8e984a5b.gif)

fontawesomeのアイコンを使用して、いいねボタンを実装しています

モデル作成・アソシエーションの定義
---
まずはモデルを作成していきます。今回、モデル名は**Like**とします。
モデルのカラムにはどのユーザーがどの記事に対していいねをしたかを判別するために、
user_idとpost_idが必要になります。

```
rails g model Like user_id:integer post_id:integer
rails db:migrate
```

```ruby:user.rb
  has_many :likes, dependent: :destroy
  has_many :like_posts, through: :likes, source: :post
```

```ruby:post.rb
  has_many :likes, dependent: :destroy
  has_many :like_users, through: :likes, source: :user
```

```ruby:like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :portfolio
end
```

:::note info
中間テーブル
has_many :中間テーブル名(任意) 
through　:経由するモデル名  
source　:関連付けの先のモデル名
:::

ルーティング設定
---

ルーティングを以下のように設定します。
どの記事に属しているかを認識させたいのでPostsにネストさせます。

また、`resources`にすると不必要なパスまで生成されてしまうので、
`resource`単数形とonlyで指定することで必要なルーティングだけ生成させます。

```ruby:routes.rb
  resource :posts do
    resources :likes, only: [:create, :destroy]
  end
```
![スクリーンショット 2022-07-02 11.12.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602337/7e6a9e9b-c365-f264-c314-14c8b9a71b20.png)
コントローラー実装
---

```
rails g controller likes create destroy
```

```ruby:likes_controller.rb
class LikesController < ApplicationController
  before_action :set_post

  def create
    like = Like.new(user_id: current_user.id, post_id: @post.id)
    like.save
  end

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

  private

  def set_post
    @post = Post.find_by(id: params[:post_id])
  end

end
```

ビューの作成
---

ビューの必要なファイルを確認します。
```
views
├── likes
│   ├──_like.html.erb
│   ├──create.js.erb
│   └──destroy.js.erb
│ 
└── posts
    └──show.html.erb
```
流れ:
**いいねアイコンをクリック**
↓
**createアクションが実行され、create.js.erbをrender**
↓
**パーシャル(_like.html.erb)を表示**

いいねを外す時も同じ流れです。

#### 記事詳細ページに「いいねボタンのパーシャル」を任意の位置に設置

```ruby:views/posts/show.html.erb
 <div id="like_<%= @post.id %>">
  <%= render 'likes/like', post: @post %>
 </div>
```
* jsを発火させるためにidを必ず指定します。　
* パーシャル内(_like.html.erb)で記事のidが必要となるので、
`@post`インスタンス変数をローカル変数`post`にして渡してあげます。

#### JavaScriptファイルを作成

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

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

>html メソッドは、要素の HTML を取得したり 指定した HTML を挿入したりすることができるメソッドです。

#### パーシャルを作成

1. ユーザーがログインしているかどうか確認
2. ユーザーがログインしている場合、対象の記事をいいねしているかどうか確認 
```erb
<% if user_signed_in? %>
 　ユーザーがログインしている場合
　　　<% if current_user.likes.find_by(post_id: post.id) %>
  　　　　　ユーザーが既に”いいね”をしている場合
　　　<% else %>
　　　　　　　　　”いいね”をしていない場合
　　　<% end %> 
<% else %>
   ユーザーがログインしていない場合
  　　▷いいねアイコンをクリックでログインページへ遷移
<% end %>
```
こうなります
↓
```erb:_like.html.erb
<% if user_signed_in? %>
   <% if current_user.likes.find_by(post_id: post.id) %>
     <%= link_to post_likes_path(post.id), method: :DELETE, remote: true do %>
       <i class="fa fa-heart like_icon"></i>
     <% end %>
   <% else %>
      <%= link_to post_likes_path(post.id), method: :POST, remote: true do %>
        <i class="fa fa-heart unlike_icon"></i>
      <% end %>
   <% end %>
<% else %>
   <%= link_to new_user_session_path do %>
      <i class="fa fa-heart unlike_icon"></i>
   <% end %>
<% end %>
```
:::note
remote: :trueを忘れずに
link_toを非同期処理にする際は、remote: :trueを追加します。
デフォルトはfalseです。
:::

```css:css
.like_icon {
  font-size: 20px;
  color: #ff69b4;
}

.unlike_icon {
  font-size: 20px;
  color: #696969;
}
```

以上でいいね機能が実装できました！