Railsにお気に入り(いいね)機能を追加する方法

✏️️ 2019/08/30 👍️2019/08/30 🔗

記事(Post Model)にFacebookやTwitterのようなお気に入り(いいね)機能を追加します。

Like Modelを作成

bin/rails g model Like user:references post:references

マイグレーションファイル

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

マイグレーションを実行

bin/rails db:migrate

アソシエーションを作成

Likeはbelongs to User and Postです。

# app/models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :post
end

Post has many Likes です。

# app/models/post.rb
 has_many :likes, dependent: :destroy

記事(post)が消えたら, likesテーブルのレコードを消したいので、dependent: :destroy にします。

User has many Likes です。

# app/models/user.rb
 has_many :likes, dependent: :destroy

ユーザ(user)が消えたら, likesテーブルのレコードを消したいので、dependent: :destroy にします。

ルーティングを追加

# config/routes.rb
resources :posts, only: %i[index show] do
  resource :like, only: %i[create destroy], module: :posts
end

resources: 複数のリソースを対象としたRESTfulインターフェイスを定義
resource: 単一のリソースを対象としたRESTfulインターフェイスを定義。resources との違いはindexが定義されないのと、 show, edit, deleteのアクションで :id を要求しないことです。

今回はcreateとdestroyアクションしか必要ないので only: %i[create destroy] にします。また、Post関連のcontrollerは app/controllers/postsにまとめるために module: :posts を指定します。

Like Controllerを作成

# app/controllers/posts/likes_controller.rb
class Posts::LikesController < ApplicationController
  before_action :authenticate_user!
  before_action :set_post

   def create
    @post.likes.where(user_id: current_user.id).first_or_create

     respond_to do |format|
      format.html { redirect_to @post }
      format.js
    end
  end

   def destroy
    @post.likes.where(user_id: current_user.id).destroy_all

     respond_to do |format|
      format.html { redirect_to @post }
      format.js
    end
  end

   private

   def set_post
    @post = Post.find_by_slug_or_id(params[:post_id])
  end
end

before_action :authenticate_user! : ログインしているユーザしかいいねできないようにしています。
first_or_create : whereに合致するレコードがある場合はそのレコードを参照し、なければ検索条件で、新しいレコードを作成します。テーブルに同じレコードを入れたくないときに便利です。
destroy_all: whereに合致するレコードが複数ある場合があるので、destroy_allで全て削除してます。

表示関連の処理を追加

ユーザがいいねしてるかを判別するメソッドを作成

# app/models/user.rb
def likes?(post)
  post.likes.where(user_id: id).any?
end

いいねボタンのtemplateファイルを作成

# app/views/posts/_like_button.html.erb
<div id="post_<%= post.id %>_likes">
  <% if user_signed_in? && current_user.likes?(post) %>
    <%= link_to (emojify ":heart:"), post_like_path(post), method: :delete, remote: true %>
  <% else %>
    <%= link_to (emojify ":heart:️"), post_like_path(post), method: :post, remote: true, style: "filter: contrast(0%);" %>
  <% end %>
  <span><%= post.likes.count %></span>
</div> 

いいねしてる場合としていない場合で場合分けしています。remote: trueで Ajax通信するようにしています。アイコンはtwemojiを利用しています。twemojiの灰色ハートアイコンがなかったので、style: "filter: contrast(0%);" で無理やり灰色にしています。

Ajax用viewファイルを作成

Ajaxで通信が来たらいいねボタンの中身をもう一度描画して、切り替えます。

# app/views/posts/likes/create.js.erb,  app/views/posts/likes/destroy.js.erb 共通
var likeElement = document.getElementById("post_<%= @post.id %>_likes")
likeElement.innerHTML = "<%=j render partial: "posts/like_button", locals: {post: @post} %>"

view file

いいねを表示させたい箇所に以下を表示

# app/views/posts/show.html.erb
<%= render partial: "posts/like_button", locals: {post: @post} if user_signed_in? %>

以上で、Railsにお気に入り(いいね)機能を追加する方法は完了です。

Akito
日本のスタートアップで主にRuby on Railsを使ってプロダクト開発をしています。