Railsにいいね機能を実装する

Railsアプリにいいね機能を実装しました。
作っているのがブログサービスなのでArticleにいいね(Like)をする形になってますが、ツイッター風アプリケーションであればArticleはTweetに置き換えて読んでください。


前提

Rails : 5.2.1

ツイッターのふぁぼ(いいね)機能のようにクリックすると赤いハートになっていいね数が1増え、再度クリックするとグレーになりいいね数が1減る、という仕様で作ります。

クリックする度にいいねボタンの部分だけ更新したいので、Javascriptを使います。


モデルの準備


Likeモデル作成

class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.integer :user_id, null: false
      t.integer :article_id, null: false

      t.timestamps
    end
  end
end

いいねをしたユーザーを記録する :user_id と、いいねを獲得した記事を記録する :article_id カラムを追加します。

Articlesテーブルにカラム追加

class AddColumnToArticles < ActiveRecord::Migration[5.2]
  def change
    add_column :articles, :likes_count, :integer
  end
end

Articlesテーブルに :likes_countというinteger型のカラムを追加します。


class Like < ApplicationRecord
  belongs_to :article, counter_cache: :likes_count
  belongs_to :user
end

counter_chaceを使うことで『子モデルの数を親モデルのカラムに保存』できます。
つまり、先ほど作成したArticleのlikes_countカラムに、Likeがいくつ付いてるのかを集計してくれるようになります。

(Article).likes_count

=> 1

このように使います。


class Article < ApplicationRecord
  has_many   :likes, dependent: :destroy
  
  def like_user(user_id)
    likes.find_by(user_id: user_id)
  end
 
end

like_userメソッドを作成します。

使い方は

if article.like_user(current_user.id) 

このようにarticleに対して、引数に入れたユーザーIDを持つlikeが存在するかどうか
つまり、(引数に入れたユーザー)は(article)にいいねをしているかどうかで処理を分岐することができます。


ルーティング


Rails.application.routes.draw do

  delete '/articles/:article_id/likes/:id', to: 'likes#destroy' ,as: :like
  
   resources :articles, :except => [:create, :show] do
     resources :likes, :only => [:create] 
   end

end

自作アプリの仕様上、deleteの際のURLにarticle_idが必要だったので上記のようになっています。
必要なければ resources :likes, :only => [:create] の部分に :destroyも加えてOKかもしれないです。(未検証)


コントローラー


Likesコントローラーは以下のようになります。

class LikesController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @like = Like.create(user_id: current_user.id, article_id: params[:article_id])
    @article.reload
  end

  def destroy
    @article = Article.find(params[:article_id])
    @like = Like.find_by(user_id: current_user.id, article_id: params[:article_id])
    @like.destroy
    @article.reload
    
  end
end

@article.reloadはクリックしていいねボタンが更新された際にいいね数も更新するために必要です。
無いと数値がおかしくなります。


今回いいね機能を表示したアクションがArticlesのshowアクションです。
以下を追記する必要がありました。

class ArticlesController < ApplicationController
  def show
    @like = Like.find_by(user_id: current_user.id, article_id: params[:id]) if user_signed_in?
  end
end


ビュー


いいね機能の部分は部分テンプレートで作成します。
その前に、後ほどその部分テンプレートを挿入していいね機能を表示する場所であるarticles#showに以下を追記します。


articles#show

<span id="article-<%= @article.id %>-like">
    <%= render 'shared/likes', article: @article, like: @like %>
</span>

"article-<%= @article.id %>-like"というIDは後で使います。内容は分かりやすいものに変更しても大丈夫です。


そして、いいね機能本体の部分テンプレートを作成します。

app/views/shared/_likes.html.erb

<% if user_signed_in? %>
  <% if article.like_user(current_user.id) %>
    <%= button_to like_path(article, like), method: :delete, remote: true do %>
      <%= image_tag("icon_red_heart.png") %>
      <span>
        <%= article.likes_count %>
      </span>
    <% end %>
  <% else %>
    <%= button_to article_likes_path(article), remote: true do %>
      <%= image_tag("icon_gray_heart.png") %>
      <span>
        <%= article.likes_count %>
      </span>
    <% end %>  
  <% end %>
<% else %>
  <%= image_tag("icon_gray_heart.png") %>
  <span>
    <%= article.likes_count %>
  </span>
<% end %>


まずif user_signed_in?でログインユーザーかどうかを切り分け、ログインしていないユーザーであればクリックできないグレーのハートといいね数を表示するようにします。

次にif article.like_user(current_user.id)で、ユーザーがその記事にいいね済かどうかを切り分けます。
いいね済であればdestroyアクション。いいねしていなければcreateアクションに飛ぶようにします。

そして次のcreateとdestroyのビューが重要です。Javascriptを使います。


create.js.erb と destroy.js.erb


app/views/likes にcreate.js.erb とdestroy.js.erbを作成します。 内容はどちらも同じです。

$("#article-<%= @article.id %>-like").html("<%= j(render 'shared/likes', article: @article, like: @like) %>")


このhtmlメソッドの動きは詳しくは下の参考リンクにありますが、
ID "article-<%= @article.id %>-like"を持つ要素を "<%= j(render 'shared/likes', article: @article, like: @like) %>" に変更する、というJavascriptのメソッドです。

つまり、グレーのハートをクリックするとまずlikeがcreateされます。
その後create.js.erbファイルの記述によってボタン部分が_likes.html.erbに再度レンダリングされます。
ということはif article.like_user(current_user.id)の判定をもう一度受けるということであり、いいねが作成されているのでdestroyへのリンク(赤いハートマーク)が表示されます。
逆も同じです。


ちなみに、(render 'shared/likes', article: @article, like: @like)の前についている j というのはescape_javascriptエイリアスで、改行と一重引用符「''」二重引用符「""」をエスケープしています。



これで、クリックするたびに表示が変わるいいねボタンを設置することができました。

参考リンク

railsとjsを使ったお手軽「いいね♡機能」

.html() | jQuery 1.9 日本語リファレンス | js STUDIO