Railsでeager_loadした後の行でfind_by, where使ってない?
要約
eager_load
やpreload
でデータを読み込んだ場合、以降の行でfind_by
, where
を使うとクエリを発行してしまう(ロードしたデータを利用しない)
【背景】
別件でサーバーの不具合原因を調査していると、eager_loadした後にも関わらずクエリが発行されていることを発見
【実行環境】
技術スタック | バージョン |
---|---|
Ruby | 3.2.2 |
Rails | 7.0.4 |
Postgresql | 1.1 |
具体例 (NG)
シナリオ
-
ログイン中のユーザーに紐づく投稿(posts)を取得し、その投稿に紐づくコメント(comments)を事前読み込み
- has_manyとかで関連づいているとする
-
前ステップで取得した各投稿に対して紐づくコメントの中から、
user_id: 5
のレコードを検索# current_userは事前にUserテーブルから取得したことにする posts = current_user.posts.eager_load(:comments) posts.map do |post| # eager_loadで読み込んだcommentから、さらに絞り込みを行いたい comments = post.comments.where(user_id: 5) puts comments end
上記の場合、ステップ2の
comments = post.comments.where(user_id: 5)
で再度Commentテーブルに対してクエリを発行してしまい、eager_loadした旨味がなくなってしまう。
解決例
detect
メソッドを使う(Rubyのメソッド)
posts = current_user.posts.eager_load(:comments)
posts.map do |post|
# 変更点
comments = construction.post.comments.detect { |comment| comment.user_id == 5 }
puts comments
end
eager_load / preloadについて
詳しくはちゃんと紹介している記事を見てほしいが、ざっくりと使用例をば
(それぞれの使い方を理解していれば、include
メソッドを使用する必要はなくなるので、あまり使わないようにしたい。)
eager_load
# レコードを連結しているので、 post, commentのカラムの値で検索をかけることができる
# (user) - post - commentの順で紐づいているもので、commentカラムの値で検索をかける
current_user.posts.eager_load(:comments).where(comments: {user_id: 5})
preload
# レコードを連結していないので、postテーブルのカラムの値のみで検索をかけることができる
# (user) - post - commentの順で紐づいているもので、commentテーブルを読み込んでおく(postに紐づくcommentのみ)
# 例1
current_user.posts.preload(:comments)
# 例2: ベースとなっているpostテーブルのカラムの値で検索はできる
current_user.posts.preload(:comments).where(post_title: "【悲報】xxxが--な件について")
# 例3 (NG): 読み込み先であるcommentテーブルのカラムの値で検索はできない
current_user.posts.preload(:comments).where(comments: {user_id: 5})
まとめ
クエリ発行が行われる際は、どんなクエリが発行されているかを確かめるべし。
最後に
間違ってたら優しく教えてください