本記事では、いいね(お気に入り)機能の実装手順を解説します。フリマアプリを題材として、あるユーザーが出品した商品に対して、非同期でいいね登録が出来ることを目標としています。
Contents
完成画面
ER図
今回、中間テーブル(Favorite)を作成・使用します。いいねをしたユーザーのid、商品のidを格納するものです。
※「User」「Item」テーブルは既に作成済みとして進めます。
いいね機能実装の流れ
いいね機能は、大まかに以下の流れで実装します。
①favoriteモデル作成
②モデル間のアソシエーション設定
③ルーティング設定
④favoriteコントローラ作成、編集
⑤ビュー実装(いいね機能用の部分テンプレート作成)
⑥非同期通信の実装
実装手順
⓪はじめに
コーディングはhaml/Scssを利用しています。
商品情報一覧が格納されている「itemsテーブル」の特定商品に「いいね」をする想定で実装を行っています。
また今回は、いいね登録されたユーザーID、商品IDを格納するための「Favorite」テーブルを作成します。そしてFavoriteテーブルは「User」テーブル、「Item」テーブルの中間テーブルの位置付けとなりますが、これらのテーブルは既に作成済みとして、解説をしていきます。
①favoriteモデル作成
まずいいね機能用のfavoriteモデルを作成します。
$ rails g model Bookmark user:references item:references
上記コマンドにより、(a)モデルファイルと(b)マイグレーションファイルが作成されます。
(a) favorite.rb
(b) xxxxx_create_favorites.rb
このまま rails db:migrate としたいところですが、その前にマイグレーションファイルに1点追加すべき処理があります。それは同じユーザーが同じ商品に対して、複数回いいね登録できないようにするものです。
そのために「t.index [:user_id, :item_id], unique: true」を追記しましょう。これにより、use_idとitem_idのセットの重複登録を防ぐことができるようになります。
class CreateFavorites < ActiveRecord::Migration[5.0]
def change
create_table :favorites do |t|
t.references :user, foreign_key: true
t.references :post, foreign_key: true
t.timestamps
t.index [:user_id, :item_id], unique: true #追加
end
end
end
最後に rails db:migrate を実行して、中間(favorite)テーブルの完成です。
$ rails db:migrate
②モデル間のアソシエーション設定
冒頭のER図を再掲します。このモデル間の関係を踏まえて、設定を進めましょう
既に出品機能は完成している前提で進めているため、新規実装するのは以下3点です。
・favoriteモデル
・itemモデルの”#いいね機能のアソシエーション処理”以下
・userモデルの”#いいね機能のアソシエーション処理”以下
その上で、各コードのポイントは次のとおりです。
#ⅰ:「belong to」「has many」について
Itemモデル・Userモデルは、Favoriteモデルに対して1対多の関係にあるため、記載のとおり定義
#ⅱ:オプション「dependent: :destroy」について
特定のitemやuserを削除した際、それに紐づくfavoriteも自動削除させるために、追加したオプション
#ⅲ:オプション「has many thorough」について
ユーザーがいいねした商品情報を直接アソシエーションで取得するために、追加したオプション
class Favorite < ApplicationRecord
belongs_to :user #ⅰ
belongs_to :item #ⅰ
end
class Item < ApplicationRecord
belongs_to :user #ⅰ
#いいね機能のアソシエーション処理
has_many :favorites, dependent: :destroy #ⅰ、ⅱ
has_many :favorite_users, through: :favorites, source: :user #ⅲ
end
class User < ApplicationRecord
has_many :items #ⅰ
#いいね機能のアソシエーション処理
has_many :favorites, dependent: :destroy #ⅰ、ⅱ
has_many :favorite_items, through: :favorites, source: :item #ⅲ
end
これでアソシエーション設定は完了です。
③ルーティング設定
次はルーティングの設定をします。
itemsとfavoritesをネスト構造としているのは、いいねは商品に紐づくためです。またfavoritesコントローラでは「createアクション」「destroyアクション」のみ使用するため、only: [:create, :destroy] と記載しています(コントローラ設定詳細は④で解説します)
Rails.application.routes.draw do
resources :items do
resource :favorites, only: [:create, :destroy]
end
end
④favoriteコントローラ作成、編集
まずは次のコマンドで、favoritesコントローラを作成しましょう。
$ rails g controller favorites create destroy
そして作成したコントローラーを、次のように編集します。
class FavoritesController < ApplicationController
before_action :set_item
def create
@favorite = Favorite.new(user_id: current_user.id, item_id: @item.id)
@favorite.save
end
def destroy
@favorite = Favorite.find_by(user_id: current_user.id, item_id: @item.id)
@favorite.destroy
end
private
def set_item
@item = Item.find_by(id: params[:item_id])
end
end
各アクションの内容の解説をします。
・set_item
いいねを押した(または現在表示している)商品情報を取得し、@itemに格納しています。
・create
current_user.favorites.newで、リソースを新規作成します。その際、user_idにはcurrent_userのidを、item_id:には@itemのidを代入します。
・destroy
基本的にcreateアクションと同様の流れです。最後に@favorite.destroyでDBから削除しています。
以上の情報で新規作成したリソースを、@favoriteに代入。それを@favorite.saveでDBに格納しています。
⑤ビュー実装(いいね機能用の部分テンプレート作成)
続いてビュー実装に取り掛かります。
全体像は次のとおりです。
1:_favorites.html.haml(部分テンプレート)を作成し、いいねボタン部分を記述
2:items/show.html.haml からrenderで1を読み込む
階層構造は以下となっています。(記述対象のファイルを★で示しています)
app
views
favorites
_favorites.html.haml★
create.js.haml
destroy.js.haml
items
show.html.haml
※「create.js.haml」「destroy.js.haml」は次のステップで使用しますので、ここでは無視してください。
.favorites{id: "favorite_#{@item.id}"}
= render 'favorites/favorite', item: @item
- if user_signed_in?
- if current_user.favorites.find_by(item_id: item.id)
= link_to item_favorites_path(item.id), method: :delete, remote: true do
.icon
= icon('fa', 'star')
.text
= 'いいね!'
.count
= item.favorites.count
- else
= link_to item_favorites_path(item.id), method: :post, remote: true do
.icon
= icon('far', 'star')
.text
= 'いいね!'
.count
= item.favorites.count
- else
= link_to new_user_session_path do
.icon
= icon('far', 'star')
.text
= 'いいね!'
.cnunt
= item.favorites.count
上から解説します。
・{id: “favorite_#{@item.id}”}
@item.idで、どの商品かを指定しています。仮に商品idが「1」の場合、id属性は「favorite_1」となりますね。これをコードの記述理由は次のステップで分かりますので、覚えておいてください。
・render ‘favorites/favorite’, item: @item
部分テンプレート「_favorite.html.haml」を呼び出します。その際、@itemを部分テンプレート内では「item」という変数名で使用します。
・if user_signed_in?、else
if “いいねしている場合、” else “いいねしていない場合” で条件分岐させます。前者の場合はいいね登録/解除処理へ、後者はログイン画面に遷移させています。
・if current_user.favorites.find_by(item_id: item.id)
現在ログイン中のユーザーIDが「1」、商品IDが「1」の場合、favoritesテーブルでuser_idが「1」と同じレコードのitem_idカラムで、item_idが「1」のものは存在しているか、を調べているようなイメージです。
・link_to item_favorites_path(item.id), method: :delete, remote: true do
存在する場合は、deleteでいいねを削除します。また「remote: true」を使用することで、リンクを押したときに、画面遷移なしでAjaxが発火すうようになります。Ajaxの実装内容については次のステップで解説します。
・link_to item_favorites_path(item.id), method: :post, remote: true do
存在しない場合は、postでいいねを作成します。deleteをpostにしただけですね。
いいね部分テンプレート作成は以上となります。完成まであと少しですので頑張りましょう。
⑥非同期通信の実装
最後に、ajaxを使用した、画面遷移せずにいいねを可能とするjavascriptファイルを作ります。
$('#favorite_#{@item.id}').html("#{escape_javascript(render "favorites/favorite", item: @item )}");
$('#favorite_#{@item.id}').html("#{escape_javascript(render "favorites/favorite", item: @item )}");
まずcreateアクション発火で実行される「create.js.haml」、destroyアクション発火で実行される「destroy.js.haml」を作成します。そしてここで、先ほどの部分テンプレート作成で、以下コードを実装したことを思い出してください。
.favorites{id: "favorite_#{@item.id}"}
= render 'favorites/favorite', item: @item
ここでは表示される商品の@item.idのを入ったid属性を生成しました。これを、いいねボタンがクリックされた時に、htmlメソッドによって新しく書き換えている訳です。
また「escape_javascript」は、javascriptファイル内にHTMLを挿入するときに必要なメソッドで、今回renderでHTMLを呼び出しているので記述しています。
最後はシンプルでしたね。以上で実装が完了です。
おわりに
非同期通信(Ajax)によるいいね機能の実装手順は以上になります。
今回はフリマアプリを題材としていますが、他サービスで実装する際も、構造は同様です。いいね機能に限らず、多対多などのテーブル構造がやや複雑になる際は、事前にER図に構造を落とし込み、全体像を理解してから実装するようにしましょう。