railsで動画サイトにいいね機能実装

# 動画サイトにいいね機能を実装してみた
今では当たり前になりつつあるいいね機能ですが、どのように作られているか気になりポートフォリオに試しに実装してみました。１ユーザーが複数の動画に対していいねをし、また１動画に対して複数のユーザーがいいねする、いわば多対多の関係なので、ユーザーと動画の中間テーブルとしていいねテーブルを作成していきます。

## 完成図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487789/d9e2953a-8207-c76d-b04a-1fcf5e16a311.png)


## 環境

rails : v5.2.4.1

〜実装済み機能〜
・動画（一覧表示、新規投稿、詳細、編集、削除）
・ユーザー（新規登録、ログイン、編集、ログアウト、詳細）
・ページネーション

## 実装手順
※いいね機能に関する部分のみコメントアウトで説明書きしています。

- ターミナルからLikeモデル作成

```
$ rails g model Like user:references video:references
$ rails db:migrate
```

```models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  validates :name, presence: true, uniqueness: true
  has_many :videos, dependent: :destroy
  # いいねはユーザーのdestroyに依存
  has_many :likes, dependent: :destroy
  # ユーザーがいいねしている動画
  has_many :liked_videos, through: :likes, source: :video
  # いいねしているかどうかを判定
  def already_liked?(video)
    self.likes.exists?(video_id: video.id)
  end
end
```

```models/video.rb
class Video < ApplicationRecord
  validates :name, :work, presence: true
  belongs_to :user
  # いいねは動画のdestroyに依存
  has_many :likes, dependent: :destroy
  # 動画にいいねしているユーザー
  has_many :liked_users, through: :likes, source: :user

  mount_uploader :work, VideoUploader
end
```

```models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :video
  # 1人が1つの動画に1いいね
  validates_uniqueness_of :video_id, scope: :user_id
end
```

- ターミナルからlikesコントローラー作成

```
$ rails g controller likes
```

- 「いいねする」「いいねを取り消す」だけなのでcreate、destroyのみ追加

```config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root "videos#index"
  resources :users, only: [:edit, :update, :show]
  # いいねを動画にネストさせる
  resources :videos do
    resources :likes, only: [:create, :destroy]
  end
end
```

```controllers/likes_controller.rb
class LikesController < ApplicationController

  def create
    # 今ログインしているユーザーによるいいね
    @like = current_user.likes.create(video_id: params[:video_id])
    # 今いる画面にリダイレクト
    redirect_back(fallback_location: root_path)
  end

  def destroy
    # 今ログインしているユーザーがいいねしている動画を探す
    @like = Like.find_by(video_id: params[:video_id], user_id: current_user.id)
    # いいねを取り消す
    @like.destroy
    # 今いる画面にリダイレクト
    redirect_back(fallback_location: root_path)
  end

end
```

```controllers/videos_controller.rb
class VideosController < ApplicationController
  
  def index
    @videos = Video.includes(:user).page(params[:page]).order("created_at DESC").per(12)
  end

  def new
    @video = Video.new
  end

  def create
    Video.create(video_params)
    redirect_to root_path
  end

  def show
    @video = Video.find(params[:id])
    # いいねする
    @like = Like.new
  end

  def edit
    @video = Video.find(params[:id])
  end

  def update
    video = Video.find(params[:id])
    video.update(video_params)
  end

  def destroy
    video = Video.find(params[:id])
    video.destroy
    redirect_to root_path
  end

  private

  def video_params
    params.require(:video).permit(:name, :work).merge(user_id: current_user.id)
  end

end

end
```

ログインしていないユーザーもいいね数を見れるようにする場合はこのように分岐が必要

```show.html.haml
-# ログインしている場合
- if user_signed_in?
  -# かつ既にいいねしている場合
  - if current_user.already_liked?(@video)
    = link_to video_like_path(@video), method: :delete do
      = icon('fa', 'heart', class: 'content__show__box__top__icons__heart__already')
  -# かつまだいいねしていない場合
  - else
    = link_to video_likes_path(@video), method: :post do
      = icon('far', 'heart', class: 'content__show__box__top__icons__heart__yet')
-# ログインしていない場合
- else
  = icon('far', 'heart', class: 'content__show__box__top__icons__heart__yet')
    = @video.likes.count
```

videoのindexコントローラーではvideoのidカラムが取得できず、エラーでnilが返されてしまう。
そのため一覧画面では表示のみとし、いいねボタンは動作しないように設定。

```index.html.haml
-# ログインしている場合、かつ既にいいねしている場合
- if user_signed_in? && current_user.already_liked?(video)
  = icon('fa', 'heart', class: 'content__box__top__icons__heart__already')
-# それ意外の場合
- else
  = icon('far', 'heart', class: 'content__box__top__icons__heart__yet')
    = video.likes.count
```

## 課題

- 動画の一覧表示（index）、ユーザーごとの詳細ページ（users/show）でもいいねできるようにする
- いちいち画面更新せず非同期で反映されるようにJqueryもしくはVueに書き換え


## 参考

- [Railsでいいね機能を実装しよう](https://qiita.com/nojinoji/items/2c66499848d882c31ffa#like%E3%83%A2%E3%83%87%E3%83%AB%E3%82%92%E3%81%A4%E3%81%8F%E3%82%8D%E3%81%86)

- [Rails いいね機能のミニアプリを作ろう](https://qiita.com/keitah/items/12b50c86bcfa9cfb4db9)

- [いいね機能のDB設計](https://d4192.hatenablog.com/entry/2019/04/16/191242)