【Rails】いいね機能の実装について

# 経緯
プログラミング歴３ケ月の初心者です。
個人的なアプリを作成した際、**非同期通信を用いたいいね機能**を導入したのですが、ググったものをそのまま使って実装してしまったので、頭の中を整理する（理解を深める）ために記事を投稿しようと思いました。

また本記事は、当機能の実装にあたり、Deviseというgemをインストールしている想定で書いていきます。
# 実装
今回は、Userモデル、Postsモデル、Likesモデルを使用します。
## マイグレーション
Likeモデルのマイグレーションを作成します。

```ruby:db/migrate/○○_create_likes.rb
class CreateLikes < ActiveRecord::Migration[5.0]
  def change
    create_table :likes do |t|
      t.references :post, foreign_key: true
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end
```
user_id、post_id共に**アソシエーションの外部キー**として設定します。

## モデル
各モデルでアソシエーションを設定していきます。

```ruby:models/user.rb
  has_many :posts
  has_many :likes
```

```ruby:models/post.rb
  belongs_to :user
  has_many :likes
```

```ruby:models/like.rb
  belongs_to :user
  belongs_to :post
```
**userは複数「いいね」**が可能ですし、
**postも複数「いいね」**される事が可能ですので、
両者とも**has_many**でlikeとアソシエーションします。

## ルート
```ruby:config/routes.rb
Rails.application.routes.draw do
  devise_for :users

  resources :users
  resources :posts do
    resources :likes, only: [:create, :destroy]
  end
end
```

postsにlikesをネストします。
ネストすることによって、どの投稿（post）にいいね（like）が付いたのか判断できるようになります。


**参考までに**　rails routesをすると…

```:ターミナル
post_likes POST   /posts/:post_id/likes(.:format)        likes#create
 post_like DELETE /posts/:post_id/likes/:id(.:format)    likes#destroy
```
特定の投稿（post）に紐づけてlikeが呼ばれるのが分かりますね。

## コントローラー
```ruby:controllers/likes_controller.rb
class LikesController < ApplicationController
  before_action :set_post

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

  def destroy
    @like = current_user.likes.find_by(post_id: @post.id)
    @like.destroy
  end

  private
  def set_post
    @post = Post.find(params[:post_id])
  end
end
```
destroyは、**現在ログインしているuserが**いいね（like）したものをアソシエーションで取得し、
その中から**いいねを解除する投稿（@post）**を探しています。

## ビュー
### 部分テンプレートの作成
今回は、いいねボタン、いいね数の表示部分については、部分テンプレートで用意します。
理由は、次項のJavaScriptの説明を見ると理解していただけると思います。

```haml:view/likes/_like.html.haml
- if Like.find_by(user_id: current_user.id, post_id: @post.id)
  = link_to "/posts/#{@post.id}/likes/#{@post.likes.ids}", method: :delete, class:"element", remote: true do
    = icon("fas", "heart", class: "like")
- else
  = link_to "/posts/#{@post.id}/likes", method: :post, class:"element", remote: true do
    = icon("fas", "heart", class: "unlike")
.count
  = @post.likes.length
```
既に**いいねしている場合はtrue**、まだ**いいねしていない場合はfalse**を返すようにしています。

**「remote: true do」は非同期通信をする場合のオプション**です。
**本来はリンク先へページが移動**しますが、「remote: true do」を記載すると、代わりに**JSを探します**。
よって、**ページの移動がストップ**されます。
（値はリンク先に飛ぶようなので、ページは移動しませんが、DBへの登録は行われるようです。）

※「icon("fas", "heart")」を使用するには、[font-awesome-sass](https://github.com/FortAwesome/font-awesome-sass)が必要となります。
※今回、cssは記載しません。

### 部分テンプレートの呼び出し

```haml:○○.haml

#like{ id: @post.id }
  = render "likes/like"
```
前項で用意した部分テンプレートを呼び出す際は、この記載をしてあげてください。
idを**カスタムデータ属性**で記載しているのがミソです。

## JavaScript
ここが非同期通信のキモになります。

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

```javascript:views/likes/destroy.js.erb
$("#like_<%= @post.id %>").html("<%= j(render 'likes/like') %>");
```
どちらも全く同じ記載です。
**「ｊ」メソッドを使用すると、JSとして見なされる**ようです。
さっき、誰かがページ移動をストップして**JSを探していました**ね。。

いいねボタンを押すと、【**like_<%= @post.id %>"**】の中身が先ほど設定した**部分テンプレートにレンダリング**されるようになります。

つまり、ページの更新を行わずとも、**部分テンプレートの「posts/like」に、非同期で更新される**ことになります。


# まとめ
最後まで読んで頂きありがとうございました。
恐らく、慣れている方からすると難しく無いのでしょうが、やはり初心者からするとすごく難しいですね…
この記事を参考にされる初学者の皆様が無事に実装できることをお祈りしてます！

今回は難しくて余裕がなかったので、おふざけなしで。。