動画サイトにいいね機能を実装してみた
今では当たり前になりつつあるいいね機能ですが、どのように作られているか気になりポートフォリオに試しに実装してみました。1ユーザーが複数の動画に対していいねをし、また1動画に対して複数のユーザーがいいねする、いわば多対多の関係なので、ユーザーと動画の中間テーブルとしていいねテーブルを作成していきます。
完成図
環境
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に書き換え