概要
「関連付けされたデータ」を読み込む時に発生する問題
例えば、「N個のデータ」と「それに関連付けされたデータ」を読み込む時に、 [N個のデータ].each(&:関連付けされたテーブルにクエリ)
というように、繰り返しクエリが発行され、Nが増えるほどレスポンスが悪くなる
対応
基本の書き方(メソッドの使い分け)
以下の4種類のメソッドがある
eager_load
、preload
、includes
、with_attached_*
ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い #Rails - Qiita
Active Record クエリインターフェイス - Railsガイド(v7.1)
ActiveRecordのincludes, preload, eager_load の個人的な使い分け - Money Forward Developers Blog
1)eager_load
- 概要:テーブルをくっつけて読み込む(
LEFT OUTER JOIN
、外部結合)。くっつけたテーブルで絞り込みができる。
eager_load < ActiveRecord::QueryMethods(v7.1) - 使い所:
has_one
、belongs_to
関連付けで使う。(テーブルをくっつけてもサイズ同等の為)
# 書き方例 # モデル class Book < ApplicationRecord belongs_to :author end # コントローラ # ⭐️以下のような書き方が出来る。(preloadだと出来ないらしい) Book.eager_load(:author).where(author: {id: 1})
2)preload
- 概要:それぞれのテーブルの内容を個々に読み込む。(「関連付けされたデータ」側は、関連する部分のみ)
preload < ActiveRecord::QueryMethods(v7.1) - 使い所:
has_many
関連付けで使う。(eager_load
だと、テーブルのサイズが爆発するかもしれない為)
# 書き方例 # モデル class Author < ApplicationRecord has_many :books end # コントローラ Author.preload(:books) # 🚨下記はエラーになるらしい。(eager_loadだとエラーにならない) Author.preload(:books).where(books: {id: 1})
3)includes
- 概要:
eager_load
かpreload
のどちらかが、内部で判断して使われている
includes < ActiveRecord::QueryMethods(v7.1) - 使い所:基本使わない方が良いらしい。コントロールしづらい為
(下記with_attached_*
の内部で使われている)
4)with_attached_*
- 概要:レコードに対応するファイルを読み込む。(Active Storageを使う)
- 使い所:
has_one_attached
またはhas_many_attached
で「ファイルの対応付け」をする場合
# 書き方例 # モデル class User < ApplicationRecord has_one_attached :avatar end # コントローラ。(pageメソッドはページネーション(kaminari)) @users = User.with_attached_avatar.order(:id).page(params[:page])
関連付けが深い場合の書き方
例えば、「本 - ユーザ - ユーザのアイコン(avatar)」の関連付けがある場合
(今回、「本 - 本の画像(thumbnail)」の関連付けも書いてある)
書き方例(モジュールを使う場合)
where
メソッドと同様に、include
やpreload
はハッシュ記法で関連テーブルからレコードを取得できる
- 下記例の
scope :with_avatar, -> { preload(user: { avatar_attachment: :blob }) }
について
- モデル間の関連付けは、[Rails5.2]ActiveStorageの仕組み(図あり)と使ってみてわかったこと #クラス図 - Qiitaのようになっている
- まず、AttachmentモデルとBlobモデルだけ考えた時、状況は、前述の
with_attached_*
そのもの。このソースは、rails/activestorage/lib/active_storage/attached/model.rb · rails/railsから以下。(バリアント画像は無いとする)
scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
- ここで、右辺に着目する。今
name
はavatar
なので、
{ includes(avatar_attachment: :blob) }
- さらに、Userモデルとの関連付けを考えると、右辺は以下になる(ハッシュ記法)
{ includes(user: { avatar_attachment: :blob) }) }
- 最後に、スコープ名を付け、
includes
の代わりにpreload
を使う。これで下記例の書き方になる
# モジュール module WithAvatar extend ActiveSupport::Concern included do scope :with_avatar, -> { preload(user: { avatar_attachment: :blob }) } end end
# Usrモデル class User < ApplicationRecord has_many :books, dependent: :destroy has_one_attached :avatar end # Bookモデル class Book < ApplicationRecord include WithAvatar belongs_to :user has_one_attached :thumbnail end # Booksコントローラ。(pageメソッドはページネーション(kaminari)) @books = Book.with_attached_thumbnail.with_avatar.order(published_at: :desc).page(params[:page])
参考
Active Record クエリインターフェイス - Railsガイド
Active Storage の概要 - Railsガイド