私の外部記憶装置

ただの覚え書きです。ちょこちょこ見直して加筆・修正していますが、間違ってるかも😅

【Rails】N+1問題(eager_load, preload, includes, with_attached_*)


概要

「関連付けされたデータ」を読み込む時に発生する問題
例えば、「N個のデータ」と「それに関連付けされたデータ」を読み込む時に、 [N個のデータ].each(&:関連付けされたテーブルにクエリ) というように、繰り返しクエリが発行され、Nが増えるほどレスポンスが悪くなる

対応

基本の書き方(メソッドの使い分け)

以下の4種類のメソッドがある
eager_loadpreloadincludeswith_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_onebelongs_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

4)with_attached_*

# 書き方例
# モデル
class User < ApplicationRecord
  has_one_attached :avatar
end

# コントローラ。(pageメソッドはページネーション(kaminari))
@users = User.with_attached_avatar.order(:id).page(params[:page])

関連付けが深い場合の書き方

例えば、「本 - ユーザ - ユーザのアイコン(avatar)」の関連付けがある場合
(今回、「本 - 本の画像(thumbnail)」の関連付けも書いてある)

書き方例(モジュールを使う場合)

# モジュール
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ガイド