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
のエイリアスで、改行と一重引用符「''」二重引用符「""」をエスケープしています。
これで、クリックするたびに表示が変わるいいねボタンを設置することができました。