MAGAZINE
ルーターマガジン
ActiveRecordのpreloadで結合先のテーブルで絞り込む方法
Railsの開発において、データベースから効率的にデータを取得するためにpreload、eager_load、includesなどのメソッドを活用することが一般的です。これらのメソッドにはそれぞれ異なる特性があり、状況に応じて使い分ける必要があります。今回は、preload
を用いて結合先のテーブルで絞り込みを行う方法について詳しく解説します。
1. preload、eager_load、includesの使い分け
まずは、preload、eager_load、includesの違いを簡単に整理しておきましょう。
-
preload:
- 複数のクエリを発行し、メインのテーブルと関連するテーブルのデータをそれぞれ取得します。
- preload自体では結合先のテーブルでの絞り込みは行えません。
-
eager_load:
- 左外部結合(
LEFT OUTER JOIN
)を利用し、1つのクエリでメインのテーブルと関連するテーブルのデータを取得します。 - 結合先のテーブルで絞り込みが可能です。
- 左外部結合(
-
includes:
- Railsが内部的に
preload
またはeager_load
を選択して実行します。 - includesにwhere条件を含めた場合、自動的にeager_loadが使用され、結合先のテーブルで絞り込みが行われます。
- Railsが内部的に
多くのブログでは、「結合先のテーブルで絞り込めるのがeager_load
とincludes
、絞り込めないのがpreload
」と説明されています。
しかし、preload
を使いながら結合先のテーブルで絞り込みを行う方法も存在します。
2. 絞り込み条件を設定したアソシエーションを使ってpreloadする
preloadを利用する際、関連するテーブルとのアソシエーションが必ず定義されている必要があります。 アソシエーションには、where条件を設定することが可能なので、結合先のテーブルで絞り込みを行いたい場合、あらかじめ絞り込み条件を設定したアソシエーションを定義し、それをpreloadに使用します。具体的には以下のような手順になります。
モデルの定義
例えば、User
モデルとPost
モデルがあり、Userがhas_many :posts
という関係にあるとします。
class User < ApplicationRecord
# 絞り込み条件なしのアソシエーション
has_many :posts
# 絞り込み条件付きのアソシエーション
has_many :published_posts, -> { where(status: 'published') }, class_name: 'Post'
end
class Post < ApplicationRecord
belongs_to :user
end
この例では、published_postsというアソシエーションを定義し、statusがpublishedのPostのみを取得するようにしています。
preloadで結合先のテーブルで絞り込めないケース
絞り込み条件なしのpostsをpreloadしてから、postsテーブルのカラムで絞り込むと、例外が発生して失敗します。
# 絞り込み条件なしでpreloadし、その後にwhereで絞り込みを試みる
users = User.preload(:posts).where(posts: { status: 'published' })
# 例外の発生
# User Load (19.5ms) SELECT `users`.* FROM `users` WHERE `posts`.`status` = 'published'
# (Object doesn't support #inspect)
preloadは別のクエリとしてpostsを取得するため、where句での絞り込みはUserテーブルに対して行われ、関連するpostsのデータは絞り込まれません。
絞り込み条件なしのアソシエーションでのpreload
例えば、postsテーブルにレコードが3行存在し、そのうちid = 3のレコードだけstatusが"published"ではなく、"draft"である場合に、絞り込み条件なしのアソシエーションであるpostsをpreloadすると、statusに関係なくすべてのレコードが参照されます。
# 絞り込み条件なしでpreload
users = User.preload(:posts)
# 実行されるクエリ
# SELECT `users`.* FROM `users`
# SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2, 3)
# 出力結果
p users.first.posts.pluck(:id, :status)
# 出力例:
# [[1, "published"], [2, "published"], [3, "draft"]]
絞り込み条件付きのアソシエーションでのpreload
次に、絞り込み条件ありのアソシエーションであるpublished_postsをpreloadした場合は以下のようになります。preloadを使用しても、結合先のテーブルのstatusが"published"のレコードのみが絞り込まれて取得されます。
# 絞り込み条件付きでpreload
users = User.preload(:published_posts)
# 実行されるクエリ
# SELECT `users`.* FROM `users`
# SELECT `posts`.* FROM `posts` WHERE `posts`.`status` = 'published' AND `posts`.`id` IN (1, 2, 3)
# 出力結果
p users.first.published_posts.pluck(:id, :status)
# 出力例:
# [[1, "published"], [2, "published"]]
3. まとめ
今回紹介した方法は、ActiveRecordのpreloadを使って結合先のテーブルで絞り込みを行うテクニックです。preloadに利用するアソシエーションに事前に絞り込み条件を設定することで、preloadを使いながらも特定の条件に合致するレコードだけを取得できます。
eager_loadはデータサイズが大きいとレスポンスタイムが増加するため、preloadを使用しながら絞り込みを行うことで、より高いパフォーマンスを維持できます。
この方法を活用することで、preloadを使いつつ効率的にデータを取得し、アプリケーションのパフォーマンスを改善することができます。
CONTACT
お問い合わせ・ご依頼はこちらから