今回は「いいね機能」の実装手順を、ミニアプリ制作を通して解説したいと思います。TwitterやInstagramでよく見かける「いいね機能」の大まかな実装手順をこの記事で学習し、ぜひ自分のアプリケーションにも導入してみてください。
いいね機能実装のポイント
まずは、いいね機能実装のポイントについて予習しておきます。ここを理解しておくと実装がグッとやりやすくなるので、ぜひ理解して上で実装に取り掛かりましょう。
例えば、ユーザー登録(= usersテーブル)と投稿機能(= postsテーブル)を持ったアプリケーションがあるとします。加えてこれら2つのテーブルは1対多の関係になっています。

ここにさらに、ユーザーが押した「いいね」を保存するテーブル(= likesテーブル)があると仮定します。その場合、これら3つのテーブル関係は以下のようになります。具体的には、「ユーザー」と「いいね」は1対多、「投稿」と「いいね」も1対多の関係となります。

ここから分かる通り、「いいね」とはユーザーと投稿のidがセットになっている状態のことを指します。反対に、どちらか片方のidが存在していなければいいね機能は実装できないので、その点に注意しましょう。
「いいね」とはユーザーと投稿のidがセットになっている状態にならなければならない。
いいね機能実装の流れ
これから実装する「いいね機能」の大まかな流れは以下のようになります。この流れを頭に入れておきながら作業していきましょう。
- アプリケーション作成
- いいね機能実装(同期いいね)
- いいね機能実装(いいね数の表示)
- いいね機能実装(非同期いいね)
- いいね機能実装(アイコン表示)
実装手順【アプリケーション作成】
アプリケーションの立ち上げ
まずは以下のコマンドでアプリケーションの立ち上げを行いましょう。データベースの作成も同時に行います。
% rails new favorite-app -d mysql
% cd favorite-app
% rails db:create
今回開発するアプリケーションの環境は以下のようになります。
- アプリケーション名:favorite-app
- Rails:6.0.3.2
- Ruby:2.6.5
- DB:MySQL
deviseを用いたユーザー登録
ユーザー登録の実装には、deviseというgemを使用します。Github通り、以下の手順でdeviseをアプリケーションに導入しましょう。
# Gemfileの下部に以下を追記
gem 'devise'
% bundle install
% rails g devise:install
% rails g devise user
% rails db:migrate
トップページの作成
続いてトップページの作成です。
「home_controller.rb」を作成し、ルーティングとビューを編集しましょう。ここまでで、ユーザー登録機能が完成です。
% rails g controller home
# routes.rb
Rails.application.routes.draw do
devise_for :users
# 以下の1行を追記
root 'home#index'
end
<%# views/home/index.html.erb %>
<% if user_signed_in? %>
<%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
<% else %>
<%= link_to 'ログイン', new_user_session_path %>
<%= link_to '新規登録', new_user_registration_path %>
<% end %>
投稿機能の実装
次は投稿機能の実装です。
投稿機能の実装には、Railsの便利機能「scaffold」を使用します。「scaffold」の使い方はここでは解説しませんので、「scaffold」について詳しく知りたい方はこちらの記事を参考にしてください。

まずは以下のコマンドで投稿機能を作成しましょう。シンプルなアプリケーション開発を目指すため、今回のカラムは「title」と「user_id」のみにしています。
% rails g scaffold post title:string user_id:integer
% rails db:migrate
現在のビューにはユーザー登録関係のリンクしかありません。「views/home/index.html.erb」に以下のコードを追記し、投稿ページへ遷移するボタンを追加しましょう。
<%# views/home/index.html.erbの最下部に以下を追記 %>
<%= link_to '投稿ページへ', posts_path %>
ここで1つ問題があります。
それは「scaffold」の機能を用いて投稿機能を作成したことで、必要のない「user_id」のフォームまで作成されてしまいました。そこで、下記の記述を削除しましょう。
<%# views/posts/_form.html.erbの以下の記述を削除してください %>
<div class="field">
<%= form.label :user_id %>
<%= form.number_field :user_id %>
</div>
<%# views/posts/index.html.erbの以下の1行を削除してください %>
<td><%= post.user_id %></td>
「posts_controller.rb」のストロングパラメーターである「user_id」も必要ないので、「:user_id」も削除しましょう。
# posts_controller.rb
def post_params
params.require(:post).permit(:title, :user_id)
end
↓ 「:user_id」を削除
def post_params
params.require(:post).permit(:title)
end
最後に、投稿時にログインしているユーザーのidを保存する記述を追記します。これは、postsテーブルには「user_idカラム」が存在しているためです。
# posts_controller.rb
def create
@post = Post.new(post_params)
# 以下の1行を追記
@post.user_id = current_user.id
respond_to do |format|
if @post.save
format.html { redirect_to @post, notice: 'Post was successfully created.' }
format.json { render :show, status: :created, location: @post }
else
format.html { render :new }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end

# 省略
private
def post_params
params.require(:post).permit(:title).merge(user_id: current_user.id)
end
上記のように、ストロングパラメーターにmergeメソッドを用いて「user_id」を付与することも可能です。
リダイレクト処理
今のままでは、ログインせずとも投稿ができてしまいます。posts_contoroller.rbに「before_action :authenticate_user!」を追加し、ログインしているユーザー以外はリダイレクトさせる処理を実装しましょう。
# posts_controller.rbに以下を追記
before_action :authenticate_user!
アソシエーションの定義
冒頭でも解説した通り、ユーザーと投稿は1対多の関係になります。UserモデルとPostモデルにアソシエーションの記述を追記しましょう。
# user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts
end
# post.rb
class Post < ApplicationRecord
belongs_to :user
end
挙動確認
ここまでで「いいね機能」を実装する前の大まかな準備ができました。ここで一旦挙動を確かめてみます。
実装手順【同期いいね】
ではここからいいね機能の実装に取り掛かります。まずは「同期いいね」です。
likesテーブルの作成
まずはユーザーIDと投稿IDを保存するため「Likeモデル」を作成しましょう。カラムは外部キーである「user_id」と「post_id」のみです。
% rails g model Like user_id:integer post_id:integer
% rails db:migrate
アソシエーションの定義
次に「usersテーブル」「postsテーブル」「likesテーブル」のアソシエーションを定義します。それぞれのテーブル関係は、下の図を参考にしてください。

# user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts
# 以下の1行を追記
has_many :likes
end
# post.rb
class Post < ApplicationRecord
belongs_to :user
# 以下の1行を追記
has_many :likes
end
# like.rb
class Like < ApplicationRecord
# 以下の2行を追記
belongs_to :user
belongs_to :post
end
「いいね」の表示
ビューでは「いいね」を押すことができるリンクを作成しましょう。
以下のように「views/posts/index.html.erb」を編集し、「いいねを外す」と「いいね」がそれぞれ表示されるよう設定します。
<%# views/posts/index.html.erb %>
<p id="notice"><%= notice %></p>
<h1>Posts</h1>
<table>
<thead>
<tr>
<th>Title</th>
<%# Userの1行を削除 %>
<th colspan="2"></th>
</tr>
</thead>
<tbody>
<% @posts.each do |post| %>
<tr>
<td><%= post.title %></td>
<%# 以下を追記 %>
<% if current_user.liked_by?(post.id) %>
<td>いいねを外す</td>
<% else %>
<td>いいね</td>
<% end %>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Post', new_post_path %>
「liked_by?」メソッドの定義
では先ほど記述した「liked_by?」メソッドをモデルに定義しましょう。whereメソッドを使用し、likesテーブルに「post_id」が存在しているかどうか検索をかけています。
# user.rbに以下を追記
def liked_by?(post_id)
likes.where(post_id: post_id).exists?
end
いいねを押す
ここまででビューに「いいね」を表示させることができました。しかし、まだリンクを付与していないため「いいね」をクリックすることができません。ここからはいいねを押す、いいねを解除できる機能を実装していきます。
まずはコントローラーの作成から行いましょう。
% rails g controller likes
作成したlikesコントローラーにcreateアクションを定義します。createアクションはいいねを押す機能のアクションです。
# likes_controller.rb
class LikesController < ApplicationController
def create
Like.create(user_id: current_user.id, post_id: params[:id])
redirect_to posts_path
end
end
ルーティングを作成しましょう。
# routes.rb
Rails.application.routes.draw do
devise_for :users
root 'home#index'
resources :posts
# 以下の1行を追記
post 'like/:id' => 'likes#create', as: 'create_like'
end

「いいね」にリンクを付与するため、ビューを編集しましょう。
<%# views/posts/index.html.erb %>
<td>いいね</td>
↓ 以下のように編集
<td><%= link_to 'いいね', create_like_path(post), method: :POST %></td>
ここまでの実装で「いいね」をクリックすることができるようになりました。
いいねを解除する
続いて、いいねを解除する機能を実装していきます。
likes_controller.rbにdestroyアクションを定義しましょう。destroyアクションはいいねを解除する機能のアクションです。
# likes_controller.rb
class LikesController < ApplicationController
def create
Like.create(user_id: current_user.id, post_id: params[:id])
redirect_to posts_path
end
def destroy
Like.find_by(user_id: current_user.id, post_id: params[:id]).destroy
redirect_to posts_path
end
end
「find_byメソッド」は、複数の検索条件を指定することができるメソッドです。
↓ 「user_id: 1, post_id: 1」を条件に指定した場合
% rails c
irb(main):001:0> Like.find_by(user_id: 1, post_id: 1)
(0.7ms) SET NAMES utf8mb4, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
Like Load (2.0ms) SELECT `likes`.* FROM `likes` WHERE `likes`.`user_id` = 1 AND `likes`.`post_id` = 1 LIMIT 1
=> #<Like id: 11, user_id: 1, post_id: 1, created_at: "2020-07-07 02:42:56", updated_at: "2020-07-07 02:42:56">
同様にルーティングも編集しましょう。
# routes.rb
Rails.application.routes.draw do
devise_for :users
root 'home#index'
resources :posts
post 'like/:id' => 'likes#create', as: 'create_like'
# 以下の1行を追記
delete 'like/:id' => 'likes#destroy', as: 'destroy_like'
end
「いいねを外す」にリンクを付与するため、ビューを編集しましょう。
<%# views/posts/index.html.erb %>
<td>いいねを外す</td>
↓ 以下のように編集
<td><%= link_to 'いいねを外す', destroy_like_path(post), method: :DELETE %></td>
これでいいねを解除することができるようになりました。
挙動確認
これで「同期いいね」を実装することができました。実際に挙動を確認してみましょう。
実装手順【いいね数の表示】
では次に、いいねが押された数を表示させましょう。
いいね数の取得には「countメソッド」を使用することで簡単に実装することができます。「index.html.erb」を以下のように編集しましょう。
<%# views/posts/index.html.erb %>
<% if current_user.liked_by?(post.id) %>
<td><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE %></td>
<% else %>
<td><%= link_to 'いいね', create_like_path(post), method: :POST %></td>
<% end %>
↓ 以下のように編集
<% if current_user.favorited_by?(post.id) %>
<td><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE %> <%= post.likes.count %></td>
<% else %>
<td><%= link_to 'いいねする', create_like_path(post), method: :POST %> <%= post.likes.count %></td>
<% end %>
これで「いいね」の右側に「いいね」の数が表示されるようになりました。
実装手順【非同期いいね】
では続いて、非同期でのいいね機能を実装していきます。ここからやや実装が複雑になってくるので、あらかじめ実装内容を解説します。
まずはそれぞれの投稿を部分テンプレートで切り出します。これは「いいね」もしくは「いいねを外す」を押した瞬間に、JavaScriptでHTML構造を置き換えるためです。

では、どの部分テンプレートを置き換えるのか判別するのでしょうか?
この問題はそれぞれの投稿に異なるidを付与してあげることで解決できます。このidが投稿を判別する指標となります。

これらを頭に入れた上で実装に進みましょう。
部分テンプレートの切り出し
まずは「いいね」の箇所を部分テンプレートで切り出します。いいね部分を切り分けることで、その部分だけを変更できるようにします。
<%# app/views/posts/index.html.erb %>
<tbody>
<% @posts.each do |post| %>
<tr>
<td><%= post.title %></td>
<% if current_user.liked_by?(post.id) %>
<td><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE %> <%= post.likes.count %></td>
<% else %>
<td><%= link_to 'いいねする', create_like_path(post), method: :POST %> <%= post.likes.count %></td>
<% end %>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
↓以下のように変更
<tbody>
<% @posts.each do |post| %>
<tr>
<%= render 'post', post: post %>
</tr>
<% end %>
</tbody>
<%# app/views/posts/_post.html.erb %>
<td><%= post.title %></td>
<% if current_user.liked_by?(post.id) %>
<td><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE %> <%= post.likes.count %></td>
<% else %>
<td><%= link_to 'いいねする', create_like_path(post), method: :POST %> <%= post.likes.count %></td>
<% end %>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
remote: trueの付与
以下のように、link_toメソッドに「remote: true」を付与します。非同期いいねでは、「remote: true」が必須なので、忘れずに付与させましょう。
<% if current_user.liked_by?(post.id) %>
<td><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE %> <%= post.likes.count %></td>
<% else %>
<td><%= link_to 'いいねする', create_like_path(post), method: :POST %> <%= post.likes.count %></td>
<% end %>
↓ 以下のように編集
<% if current_user.liked_by?(post.id) %>
<td><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE, remote: true %> <%= post.likes.count %></td>
<% else %>
<td><%= link_to 'いいねする', create_like_path(post), method: :POST, remote: true %> <%= post.likes.count %></td>
<% end %>
「remote: true」を付与することで、パラメーターがHTML形式ではなくJS形式で送られるようになります。
今回の場合は「remote: true」をlink_toメソッドに付与することで、likes_controller.rbのcreateアクション後は、views/likes/create.js.erbが呼び出されます。
redirect_toの削除
likes_controller.rbの「redirect_to」の記述を削除しましょう。「redirect_to」を記述すると画面遷移が行われてしまい、非同期処理ができなくなってしまうためです。
また「before_action」を使用して、投稿のidを取得できるようにしましょう。
# likes_controller.rb
class LikesController < ApplicationController
def create
Like.create(user_id: current_user.id, post_id: params[:id])
redirect_to posts_path
end
def destroy
Like.find_by(user_id: current_user.id, post_id: params[:id]).destroy
redirect_to posts_path
end
end
↓ 以下のように変更
class LikesController < ApplicationController
before_action :post_params
def create
Like.create(user_id: current_user.id, post_id: params[:id])
end
def destroy
Like.find_by(user_id: current_user.id, post_id: params[:id]).destroy
end
private
def post_params
@post = Post.find(params[:id])
end
end
js.erbファイルの作成
では続いて、createアクションとdestroyアクションのjs.erbファイルをそれぞれ作成しましょう。今回はjs.erbファイルがきちんと発火するかどうかを確認するため、alertを記載しておきます。
<%# app/views/likes/create.js.erb %>
alert('いいねが押せている!');
<%# app/views/likes/destroy.js.erb %>
alert('いいねを解除できている!');
このように非同期でアラート表示がされれば成功です。
指標の作成(idの付与)
次に部分テンプレートを切り替える部分にidを付与しましょう。具体的には「id=”post_<%= post.id %>”」と記述することで、投稿それぞれ異なるidを付与します。
<%# app/views/posts/index.html.erb %>
<tbody>
<% @posts.each do |post| %>
<tr>
<%= render 'post', post: post %>
</tr>
<% end %>
</tbody>
↓ idを付与
<tbody>
<% @posts.each do |post| %>
<tr id="post_<%= post.id %>">
<%= render 'post', post: post %>
</tr>
<% end %>
</tbody>
これで部分テンプレートを切り替えるための指標を作ることができました。
js.erbファイルの編集
では部分テンプレートの切り替えができるように、それぞれのjs.erbファイルを編集しましょう。「innerHTMLメソッド」を使用することで、特定のHTML要素を置き換えることができます。
<%# app/views/likes/create.js.erb %>
document.getElementById('post_<%= @post.id %>').innerHTML = '<%= j(render @post) %>'
<%# app/views/likes/destroy.js.erb %>
document.getElementById('post_<%= @post.id %>').innerHTML = '<%= j(render @post) %>'
「j」とは「escape_javascriptメソッド」の省略形で、部分テンプレート内の改行をエスケープ処理してくれます。
省略せず、以下のように記述することもできます。
document.getElementById('post_<%= @post.id %>').innerHTML = '<%= escape_javascript(render @post) %>'
挙動確認
ここまでの実装で「非同期いいね」が実装できました。
実装手順【アイコン使用】
ここからはTwitterやInstagramのような、アイコンを用いたいいね機能を実装していきます。アイコンにはFontAwesomeを使用します。
FontAwesome:https://fontawesome.com/
FontAwsomeの導入
まずはFontAwesomeの導入から行います。詳しくは解説しませんので、手順通り進めていきましょう。
まずはGemの導入です。
# Gemfileに以下を追記
gem 'font-awesome-sass'
% bundle install
続いて「application.css」を「application.scss」に変更します。その後以下のコードを追記しましょう。
// application.scssに以下の2行を追記(既存のコードは消さないでください)
@import "font-awesome-sprockets";
@import "font-awesome";
これでコード内でFontAwesomeを使用することができるようになりました。
Rails6でのFontAwesomeの導入は、以下の記事を参考にしてください。

アイコンの表示
次にアイコンをビュー上で表示させましょう。「いいね」と「いいね解除」を区別するため、それぞれのアイコンに「like-btn」と「unlike-btn」というクラス名を付与しています。
また、link_toメソッドにも「like-link」というクラスを付与している点にも注意してください。
<%# app/views/posts/_post.html.erb %>
<td><%= post.title %></td>
<% if current_user.liked_by?(post.id) %>
<td><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE, remote: true %> <%= post.likes.count %></td>
<% else %>
<td><%= link_to 'いいねする', create_like_path(post), method: :POST, remote: true %> <%= post.likes.count %></td>
<% end %>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
↓ 以下のように編集
<td><%= post.title %></td>
<% if current_user.liked_by?(post.id) %>
<td>
<%= link_to destroy_like_path(post), class: "like-link", method: :DELETE, remote: true do %>
<i class="fa fa-heart unlike-btn"></i>
<% end %>
<%= post.likes.count %>
</td>
<% else %>
<td>
<%= link_to create_like_path(post), class: "like-link", method: :POST, remote: true do %>
<i class="fa fa-heart like-btn"></i>
<% end %>
<%= post.likes.count %>
</td>
<% end %>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
デザインの変更
最後にCSSを用いてアイコンの色を変更していきます。
// likes.scss
.like-link {
text-decoration: none;
}
.like-link:hover {
background-color: #fff!important;
}
.like-btn {
font-size: 15px;
color: #808080;
}
.unlike-btn {
font-size: 15px;
color: #e54747;
}
Scaffoldでアプリケーションを作成すると、以下のような記述がデフォルトで入ってしまいます。
a {
color: #000;
&:visited {
color: #666;
}
&:hover {
color: #fff;
background-color: #000;
}
}
アイコンをhoverした際に、背景が黒になってしまうのを避けるため「!important」を使用してコードを上書きしています。
.like-link:hover {
background-color: #fff!important;
}
挙動確認
これでアイコンでのいいね表示が完成しました。
参考記事
escape_javascriptメソッド:
https://api.rubyonrails.org/classes/ActionView/Helpers/JavaScriptHelper.html
innerHTMLメソッド:
https://www.nishishi.com/javascript-tips/innerhtml.html
FontAwesomeの導入:
https://qiita.com/tai-fuj/items/fdb129866c03ae44ea93
!importantの使い方:
https://techacademy.jp/magazine/9424
今回は「同期いいね機能」「いいね数の表示」「非同期いいね機能」「アイコンいいね機能」の4つをそれぞれ解説しました。いいね機能に関してはほぼ網羅したので、ぜひ理解して自身のアプリケーションにも組み込んでみてください。