LoginSignup
26
16

More than 1 year has passed since last update.

【Rails】いいね機能の作り方

Last updated at Posted at 2021-09-19

プログラミングスクールの最終課題でオリジナルアプリを作成するにあたり、
いいね機能を実装したので、アウトプットしていきます。
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を外部キーとして設定します。

db/migrate/*****_create_favorites.rb
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

②各モデルにアソシエーションを記述する

app/models/user.rb
class User < ApplicationRecord
  has_many :posts, dependent: :destroy
  has_many :favorites, dependent: :destroy
end
app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :favorites, dependent: :destroy
end
app/models/favorite.rb
class Favorite < ApplicationRecord
  belongs_to :user
  belongs_to :post
end

dependent: :destroyは、親のレコードを削除した際に関連する子のレコードも全て削除するための記述です。
例えば「投稿Aを削除したのに、favoritesテーブルには投稿Aに関するいいねのレコードが残っている…」となるとテーブルの管理がしづらくなってしまいます。
これを解消するためにdependent: :destroyを用いて、「投稿Aが削除されたら、投稿Aに関連するfavoritesテーブルのレコードを削除する」という設定をしておきます。

③URIパターンに「:post_id」を含めたルーティングを設定する

favoritesコントローラーのルーティングを設定します。

config/routes.rb
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の違いについて

ここでfindfind_byの違いを整理しておきます。
すでにご存知の方は読み飛ばしてください。

findメソッド
モデルの主キー(id)を引数に指定することで、該当するレコードを取得できるメソッド。
ここでいう「主キー(ID)」とは、レコードの一番左側にあるidのこと。
Post.find(1)Post.find(params[:id])のように、どのid(主キー)か分かっている場合は、findメソッドを用いることができます。

find_byメソッド
モデルの主キー以外の条件でも、レコードを取得することができるメソッド。
(主キー(id)でも検索・取得可能)
今回のuser_idpost_idのようなid(主キー)か不明で、別の条件でレコードを検索したい場合はfind_byメソッドを用います。

⑤Postモデルに「ログイン中のユーザーがその投稿に対していいねをしているか」を判断するメソッドを定義する

app/models/post.rb
def favorited?(user)
   favorites.where(user_id: user.id).exists?
end

いいねボタンがクリックされる度に、毎回呼び出すメソッドです。

メソッド内の構成
favorited?(user)として、メソッドに引数を指定する。
 →次のステップでcurrent_userを指定します。
②「その引数(current_user)のidと等しいuser_idを持つレコードは、favoritesテーブル内に存在するか?」をexists?を用いて判断します。

これによりいいねボタンがクリックされた際、
・一致するレコードが存在しない=「まだいいねしていない→createアクションへ」
・一致するレコードが存在する =「すでにいいね済み→destroyアクションへ」
と分岐させることができます。

⑥投稿詳細のビューファイルにいいねボタンを実装する

app/views/posts/show.html.erb
<% 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メソッドを確認してみると、それぞれPOSTDELETEであることが分かります。

post_favorites POST   /posts/:post_id/favorites(.:format)      favorites#create
post_favorite DELETE  /posts/:post_id/favorites/:id(.:format)  favorites#destroy

そのためlink_toメソッド内で、method: :postmethod: :deleteとしてHTTP通信の種類を指定する必要があります。

これでいいね機能の完成です!お疲れ様でした!
ご指摘等あれば、ご教授頂けますと幸いです。

ハートをFontAwesomeのアイコンにしたい方は、自分が書いたこちらの記事をご参照ください。
【Rails】FontAwesomeの使い方/アイコンにアニメーションを付ける

【参考にした記事】
いいね機能の実装
find, find_by, find_by_sqlメソッドの違いについて

26
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
16