プログラミングスクールの最終課題でオリジナルアプリを作成するにあたり、
いいね機能を実装したので、アウトプットしていきます。
Qiitaへの投稿に不慣れなため、稚拙な部分もありますがご容赦ください。
前提条件
・Rubyバージョン2.6.5
・Railsバージョン6.0.0
・「gem 'devise'によるUserモデル」と「投稿に関するPostモデル」は作成済みとします。
いいね機能作成の流れ
下記の6ステップに分けていいね機能を実装していきます。
①いいね機能を管理する**「Favoriteモデル」を作成する。
②各モデルにアソシエーションを記述する。
③URIパターンに「:post_id」を含めたルーティングを設定する。
(どの投稿に紐付くいいねか分かるようにするため、postsコントローラーのルーティングに
ネストさせる)
④favoritesコントローラーでアクションを定義する。
⑤Postモデルに「ログイン中のユーザーがその投稿に対していいねをしているか」**
を判断するメソッドを定義する。
⑥投稿詳細のビューファイルにいいねボタンを実装する。
①いいね機能を管理するFavoriteモデルを作成する
まずはrails g model
でFavoriteモデルを作成します。
rails g model favorite
次に生成されたマイグレーションファイルにカラムを設定します。
この時アソシエーションの観点からいいね機能を考えると、
・一人のUserは複数の投稿にいいねできる
・一つのPostは複数のユーザーからのいいねを持つ
という多対多の関係性であることが分かります。
この場合、「Userのid」と「Postのid」を結び付ける中間テーブルを作成する必要があるため、
UserモデルとPostモデルそれぞれのidを外部キーとして設定します。
class CreateFavorites < ActiveRecord::Migration[6.0]
def change
create_table :favorites do |t|
t.references :user, null: false, foreign_key: true
t.references :post, null: false, foreign_key: true
t.timestamps
end
end
end
マイグレーションファイルを編集したため、変更をデータベースへ反映します。
rails db:migrate
②各モデルにアソシエーションを記述する
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :favorites, dependent: :destroy
end
class Post < ApplicationRecord
belongs_to :user
has_many :favorites, dependent: :destroy
end
class Favorite < ApplicationRecord
belongs_to :user
belongs_to :post
end
dependent: :destroy
は、親のレコードを削除した際に関連する子のレコードも全て削除するための記述です。
例えば「投稿Aを削除したのに、favoritesテーブルには投稿Aに関するいいねのレコードが残っている…」となるとテーブルの管理がしづらくなってしまいます。
これを解消するためにdependent: :destroy
を用いて、「投稿Aが削除されたら、投稿Aに関連するfavoritesテーブルのレコードを削除する」という設定をしておきます。
③URIパターンに「:post_id」を含めたルーティングを設定する
favoritesコントローラーのルーティングを設定します。
resources :posts do
resource :favorites, only: [:create, :destroy]
end
「Userがいいねしたのは、どの投稿なのか」を分かるようにするため、postsコントローラーのルーティングにネストさせます。
これにより、rails routes
では下記のようになります。
post_favorites POST /posts/:post_id/favorites(.:format) favorites#create
post_favorite DELETE /posts/:post_id/favorites/:id(.:format) favorites#destroy
ルーティングに:post_id
が含まれているのが分かります。
この:post_id
が、「どの投稿なのか」を示す役割を果たします。
④favoritesコントローラーでアクションを定義する
次にfavoritesコントローラーを作成します。
rails g controller favorites
それではfavoritesコントローラーに、いいねボタンが押された時に対応するアクションを記述していきます。
createアクション
def create
@post_favorite = Favorite.new(user_id: current_user.id, post_id: params[:post_id])
@post_favorite.save
redirect_to post_path(params[:post_id])
end
ポイントの整理
・user_id
には、ログイン中のユーザーidを指定する。
・post_id
には、ステップ③でルーティングに含めた「:post_id」
を指定する。
→これにより、「ログイン中のユーザーが、どの投稿にいいねしたのか」が分かるようになる。
・saveメソッドでデータベースへ保存したのち、再度投稿詳細画面へリダイレクトする。
destroyアクション
def create
@post_favorite = Favorite.find_by(user_id: current_user.id, post_id: params[:post_id])
@post_favorite.destroy
redirect_to post_path(params[:post_id])
end
ポイントの整理
・user_id
には、ログイン中のユーザーidを指定する。
・post_id
には、ステップ③でルーティングに含めた「:post_id」
を指定する。
・destroyメソッドでデータベースのレコードを削除したのち、再度投稿詳細画面へリダイレクトする。
#補足:findとfind_byの違いについて
ここでfind
とfind_by
の違いを整理しておきます。
すでにご存知の方は読み飛ばしてください。
findメソッド
モデルの主キー(id)を引数に指定することで、該当するレコードを取得できるメソッド。
ここでいう「主キー(ID)」とは、レコードの一番左側にあるidのこと。
Post.find(1)
やPost.find(params[:id])
のように、どのid(主キー)か分かっている場合は、findメソッドを用いることができます。
find_byメソッド
モデルの主キー以外の条件でも、レコードを取得することができるメソッド。
(主キー(id)でも検索・取得可能)
今回のuser_id
やpost_id
のようなid(主キー)か不明で、別の条件でレコードを検索したい場合はfind_byメソッドを用います。
⑤Postモデルに**「ログイン中のユーザーがその投稿に対していいねをしているか」**を判断するメソッドを定義する
def favorited?(user)
favorites.where(user_id: user.id).exists?
end
いいねボタンがクリックされる度に、毎回呼び出すメソッドです。
メソッド内の構成
①favorited?(user)
として、メソッドに引数を指定する。
→次のステップで**current_user
を指定**します。
②「その引数(current_user)のidと等しいuser_id
を持つレコードは、favoritesテーブル内に存在するか?」をexists?
を用いて判断します。
これによりいいねボタンがクリックされた際、
・一致するレコードが存在しない=「まだいいねしていない→createアクション
へ」
・一致するレコードが存在する =「すでにいいね済み→destroyアクション
へ」
と分岐させることができます。
⑥投稿詳細のビューファイルにいいねボタンを実装する
<% if @post.favorite?(current_user) %>
# 一致するレコードが存在する=すでにいいね済みの場合はdestroyアクションへ
<%= link_to post_favorite_path(@post.id), method: :delete do %>
<span style="color:red;">❤︎</span>
<% end %>
<% else %>
# 一致するレコードが存在しない=まだいいねしていない場合はcreateアクションへ
<%= link_to post_favorites_path(@post.id), method: :post do %>
<span>❤︎</span>
<% end %>
<% end %>
投稿のインスタンス変数@post
に対して、⑤で定義したfavorited?メソッド
を呼び出し、
引数にはログイン中のユーザー(current_user)を指定します。
これにより、メソッドの返り値がtrueの場合はdestroyアクション
、falseの場合はcreateアクション
が実行されることになります。
HTTPメソッドに関する注意点
link_toメソッド
のHTTPメソッドには、GET
がデフォルトで指定されています。
一方rails routes
を用いて、createアクション
とdestroyアクション
のHTTPメソッドを確認してみると、それぞれPOST
とDELETE
であることが分かります。
post_favorites POST /posts/:post_id/favorites(.:format) favorites#create
post_favorite DELETE /posts/:post_id/favorites/:id(.:format) favorites#destroy
そのためlink_toメソッド
内で、method: :post
とmethod: :delete
としてHTTP通信の種類を指定する必要があります。
これでいいね機能の完成です!お疲れ様でした!
ご指摘等あれば、ご教授頂けますと幸いです。
ハートをFontAwesomeのアイコンにしたい方は、自分が書いたこちらの記事をご参照ください。
【Rails】FontAwesomeの使い方/アイコンにアニメーションを付ける