技術系ブログ

とにかく小ネタで

【rails】いいね機能実装 (追記あり)

参考:【Rails】いいね機能の実装手順 | たみずブログ

現在railsアプリを製作中です。いいねの実装ができたので残しておきます。

注意!deviseを導入済みで進ます

Likeテーブルを導入

rails g model Like user:references post:references
rails db:migrate

これにより

class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.references :user, foreign_key: true
      t.references :post, foreign_key: true

      t.timestamps
    end
  end
end

ができます 次にそれぞれのモデルに書いていきます

Likeモデル

class Like < ApplicationRecord
  validates :user_id, presence: true, uniqueness: {scope: :post_id}
  validates :post_id, presence: true
  belongs_to :post
  belongs_to :user
end

uniquenessヘルパーには一意性チェックの範囲を限定する別の属性を指定する:scopeオプションがあります
参考: Active Record バリデーション - Railsガイド Postモデル

class Post < ApplicationRecord
    belongs_to :user
    has_many :likes, dependent: :destroy
end

dependent: :destroyと書くのはpostが削除されたらlikeも消すようにするため
参考: has_manyのdependentオプションについて【Ruby on Rails】 - Qiita

最後にUserモデル

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
          :recoverable, :rememberable, :validatable
  has_many :posts, dependent: :destroy
  has_many :likes, dependent: :destroy
end

ルーティングの設定

config/routes.rb

Rails.application.routes.draw do
   resources :posts do
    resources :likes, only: [:create, :destroy]
  end
end

参考: Rails のルーティング - Railsガイド

このようにネストした書き方か、progeteにあるような
post 'likes/:post_id/create', to: 'likes#create'と書かないとparamsでidが取得できなくなります。要注意です。
ネストした書き方だとpost_likes POST /posts/:post_id/likes(.:format) likes#createと自動的にやってくれますしpathも使えます。
ネストしないと/likes ,to: 'likes#create'となってしまい、不可能になってしまいます。

viewの作成

rails g controller likes create destroy

コントローラー側

class LikesController < ApplicationController
  before_action :set_post, only: [:create, :destroy]
  def create
    @like = current_user.likes.create(like_params)
    redirect_to post_path(@post)
  end

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

  private
  def set_post
    @post = Post.find(params[:post_id])
  end
  def like_params
    params.permit(:post_id)
  end
end
Postsコントローラの設定
  def show
    @like = Like.new
  end

今回はPosts#showでいいね出来るようにするのでshowアクションに以下を付け加える

view側

touch app/views/likes/_like.html.slim

-if current_user.likes.find_by(post_id: @post.id)
  = link_to post_like_path(@post), method: :delete
    p いいね済
- else
  = link_to post_likes_path(@post),method: :post
    p いいね

部分テンプレート

app/views/posts/show.html.slim

.....
    = render "likes/like"

おわり。

追記

いいねをしたpostのみをユーザーページに表示させるには

#app/controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    @liked_post = Post.joins(:likes).where(likes: { user: @user })
  end
end

@liked_post = Post.includes(:user).joins(:likes).where(likes: { user: @user })解説
参考: ActiveRecordのincludes、joins、eager_load等の違いを調べてみた - Qiita
Rails における内部結合、外部結合まとめ - Qiita
似ているようで全然違う!?Activerecordにおけるincludesとjoinsの振る舞いまとめ - Qiita
ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い - Qiita

SELECT "posts".* FROM "posts" INNER JOIN "likes" 
ON "likes"."post_id" = "posts"."id" 
WHERE "likes"."user_id" = $1  [["user_id", 1]]
実際に導入してlogを見るとこの様になっています。 
絞り込みたいのは,いいねをした@userのlikeデーブルにあるuser_idに紐付いたpostです。。
INNER JOIN 内部結合
ON 結合キー

実際に表示させるには

#app/views/users/show.html.slim
  - @liked_post.each do |post|
    p タイトル : #{post.name}

非同期導入

↓続きはこちら 【rails】いいね機能 非同期編 - 技術系ブログ

おしまし。