【Rails】いいね機能の実装（非同期）

# 目的
Railsで作成したアプリに非同期いいね機能を実装する。

# 開発環境
macOS: *Big Sur*
Rubyバージョン: *2.6.5*
Railsバージョン: *6.0.0*

# 前提

- アプリ`test-app`が作成されている。
[【Rails】簡単な投稿アプリの作成](https://qiita.com/oak1331/items/18a4f3a3386aab0fa354)
- `devise`が導入されている。
[【Rails】ユーザー管理機能(devise)の導入](https://qiita.com/oak1331/items/0759bd297e9fd3b81ed2)
- `jQuery`が導入されている。
[【Rails】iQueryの導入](https://qiita.com/oak1331/items/68d301cf0c448bb29356)
- `Font Awesome`が導入されている。
[【Rails】FontAwesomeの導入](https://qiita.com/oak1331/items/f8caf42566f938f28abe)

# 手順

1. [はじめに](#はじめに)
1. [ルーティングの設定](#ルーティングの設定)
1. [Likeモデルの作成](#likeモデルの作成)
1. [アソシエーションの設定](#アソシエーションの設定)
1. [バリデーションの設定](#バリデーションの設定)
1. [メソッドの定義](#メソッドの定義)
1. [likesコントローラーの作成](#likesコントローラーの作成)
1. [アクションの定義](#アクションの定義)
1. [いいねアイコンの表示](#いいねアイコンの表示)
1. [jsファイルの作成](#jsファイルの作成)


# はじめに

今回は非同期でのいいね機能を実装していきます。
工程は多いですが、やっていることは複雑ではないため、初学者さんでもしっかり読めばできる内容かと思います。

# ルーティングの設定

それでは早速始めていきます！

まずルーティングをネストさせ、設定します!

```ruby:routes.rb
Rails.application.routes.draw do
#省略
  resources :posts, do
    resources :likes, only: [:create, :destroy]
  end
end
```

# Likeモデルの作成

次に下記コマンドで`Likeモデル`を作成します。

```:ターミナル
% rails g model like
```

今回は`どの投稿に誰がいいねしたか`を記録したいので、`user_id`と`post_id`をカラムに追加します。

```ruby:db/migrate/20XXXXXXXXXXXX_create_likes.rb
class CreateLikes < ActiveRecord::Migration[6.0]
  def change
    create_table :likes do |t|
      t.references :user, null: false, foreign_key: true
      t.references :post, null: false, foreign_key: true
      t.timestamps
    end
  end
end
```

記述できたらマイグレートします。

```:ターミナル
% rails db:migrate
```

以上でモデルは完成です!

# アソシエーションの設定

続いてアソシエーションの設定です。

```ruby:app/models/user.rb
class User < ApplicationRecord
#省略
  has_many :likes, dependent: :destroy
  has_many :liked_posts, through: :likes, source: :post
end
```

```ruby:app/models/post.rb
class Post < ApplicationRecord
#省略
  has_many :likes, dependent: :destroy
  has_many :liked_users, through: :likes, source: :user
end
```

```ruby:app/models/like.rb
class Post < ApplicationRecord
#省略
  belongs_to :user
  belongs_to :post
end
```

これでアソシエーションの設定ができました!

# バリデーションの設定

次はバリデーションの設定です。
1人が1つの投稿に対して、1つしかいいねをつけられないようにします。

```app/models/like.rb
class Like < ApplicationRecord
#省略
  validates_uniqueness_of :post_id, scope: :user_id
end
```

`validates_uniqueness_of`によって、`post_id`と`user_id`　の組は1組しかできないようになりました。

# メソッドの定義

いいねしているか否かという条件分岐の際に使用するメソッドを定義します。

```ruby:app/models/user.rb
# 省略

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

whereメソッドを使用し、likesテーブルに`post_id`が存在しているかどうか検索をかけています。
`exists?メソッド`は、該当の値があればtrue、なければfalseを返すメソッドです。

# likesコントローラーの作成

コントローラーを作成します。

```:ターミナル
% rails g controller likes
```

# アクションの定義

次にアクションの定義です。

```ruby:likes_controller.rb
class LikesController < ApplicationController
  before_action :authenticate_user!, only: [:create, :destroy]
  before_action :post_params, only: [:create, :destroy]

  def create
    Like.create(user_id: current_user.id, post_id: @post.id)
  end

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

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

`create`と`destroy`アクションを定義していきます。

`find_byメソッド`は、複数の検索条件を指定することができるメソッドです。

# いいねアイコンの表示

次に部分テンプレートを切り替える部分にidを付与します!
具体的には`「id = "like_<%= @post.id %>"」`と記述することで、投稿それぞれ異なるidを付与します。

```html:app/views/posts/show.html.erb
<div id = "like_<%= @post.id %>">
  <%= render partial: "likes/like", locals: { post: @post } %>
</div>
```

部分テンプレートです。

```html:app/views/likes/_like.html.erb
<% if !user_signed_in? %>
  <i class="fa fa-heart like-btn"></i>
  <%= post.likes.length %>
<% elsif current_user.liked_by?(post.id) %>
  <%= link_to post_like_path(post, post.likes), class: "like-link", method: :delete, remote: true do %>
    <i class="fa fa-heart unlike-btn"></i>
  <% end %>
  <%= post.likes.length %>
<% else %>
  <%= link_to post_likes_path(post), class: "like-link", method: :post, remote: true do %>
    <i class="fa fa-heart like-btn"></i>
  <% end %>
  <%= post.likes.length %>
<% end %>
```

`remote: true`を付与することでパラメーターをJS形式で送られ、createアクション後は、`create.js.erb`が呼び出されます。

```css:app/assets/stylesheets/likes.scss
.like-link {
  text-decoration: none;
}

.like-btn {
  font-size: 1em;
  color: #808080;
}
 
.unlike-btn {
  font-size: 1em;
  color: #e54747;
}
```

`いいねした時`と`いいねしていない時`で色を変えまてます。

# jsファイルの作成

いいね機能を非同期で行うための`jsファイル`を作成します。

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

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

これで非同期でのいいね機能が実装できたと思います。
確認してみてください！

# 最後に

以上で、いいね機能の実装は完了です。
長くなってしまいましたが、一つ一つ理解していけば実装できると思いますので、ぜひ試してみてください。
では。