読んで欲しい人
- Railsでいいね機能を実装していて
- ビューのループ中でクエリを発行するメソッドを使用している人
- SQLクエリがたくさん出てきて、n+1問題が発生している人
- 過去の自分
動作環境
- MacOS 14.5
- ruby 3.3.0
- Rails 7.1.3.3
- psql (PostgreSQL) 14.11
作っているアプリの構造
概要
- 小さいX(旧ツイッター)みたいなアプリ
- ログイン機能
- 短文の投稿機能
- いいね機能
テーブル設計
エラー・課題
app/models/user.rb:23:in `already_liked?'
16:48:02 web.1 | Rendered posts/_like_btn.html.haml (Duration: 1.6ms | Allocations: 609)
16:48:02 web.1 | Rendered posts/_follow_btn.html.haml (Duration: 0.0ms | Allocations: 12)
16:48:02 web.1 | Like Exists? (0.3ms) SELECT 1 AS one FROM "likes" WHERE "likes"."user_id" = $1 AND "likes"."post_id" = $2 LIMIT $3 [["user_id", 2], ["post_id", 5], ["LIMIT", 1]]
↑
これがめっちゃ出てくる。
クエリがたくさん発行されてn+1問題になってしまっている
原因
- いいねボタンを表示する時に
already_liked?
が何回も実行されて、投稿の回数分のクエリが発行されてしまっている
いいねボタンを表示する仕組み
_like_btn.html.haml
# いいねボタンを表示するファイル
-if current_user.already_liked?(post)
= button_to post_likes_path(post_id: post), method: :delete do
%i.fa-regular.fa-hear # → いいねしてない時のアイコン
- else
= button_to post_likes_path(post_id: post), method: :post do
%i.fa-solid.fa-heart # → いいねしてる時のアイコン
already_liked?メソッド
- 自分がその投稿をいいねしているのか、していないのかを判定するメソッド
user.rb
# モデルファイル
def already_liked?(post)
likes.exists?(post_id: post.id)
end
exists?メソッドとは
-
指定した条件のレコードがデータベースに存在するかどうかをture/falseで返すメソッド
-
SQLを発行するメソッド
# 使い方
Like.exists?(post_id: 3)
# 発行されるSQL
#SELECT 1 AS one FROM "likes" WHERE "likes"."post_id" = $3 LIMIT $1
解決方法
- 事前に配列でlikesテーブルからpost_idの配列を取得して、include?メソッドでいいね判定を行うようにする
likes_controller.rb
#コントローラ
def index
@liked_post_ids = current_user ? current_user.likes.pluck(:post_id) : []
end
view
#ビュー画面で使うとき
-if liked_post_ids.include?(post.id)
= button_to post_likes_path(post_id: post), method: :delete do
%i.fa-regular.fa-hear # → いいねしてない時のアイコン
- else
= button_to post_likes_path(post_id: post), method: :post do
%i.fa-solid.fa-heart # → いいねしてる時のアイコン
pluckメソッドとは
- テーブルから指定したカラムを取得するクエリを送信できる
# 使い方
Like.pluck(:post_id)
# 発行されるSQL
#Like Pluck (0.9ms) SELECT "likes"."post_id" FROM "likes"
# 戻り値:post_idを配列にしたもの
[1, 2, 3, 5]
※current_userがない時にから配列を返している理由
- nilに対して
.include?
をするとエラーになってしまうため
学び
-
コードを記述するときは、そのメソッドがどんなクエリを発行しているのか?を気にして書くこと
-
DBとのやり取りを意識してコードを書くこと
参考記事