Railsのincludesを使用したN+1対策
eager_loadingを使用してN+1を解消するメソッド
リレーションで取得するオブジェクトを関連元のオブジェクトに事前にキャッシュさせておき、リレーションでの取得が行われた時にキャッシュを利用して取得を行う
loaded? メソッドでキャッシュされているか確認できる
ActiveRecord::Relationのメソッドなのでwhere等の後で呼べる(ActiveRecordオブジェクト)
例
code: (rb)
class User < ApplicationRecord
has_many :blogs
end
class Blog < ApplicationRecord
belongs_to :user
end
includes無し
code: (rb)
users = User.all
users.each do |user|
user.blogs.each do |blog|
end
end
blogの数だけSELECTが発生する
code: (sql)
SELECT * FROM users;
SELECT * FROM blogs WHERE user_id = 1;
SELECT * FROM blogs WHERE user_id = 2;
includes有り
code: (rb)
# includesでeager_loading
users = User.includes(:blogs).all
users.each do |user|
user.blogs.each do |blog|
end
end
usersにキャッシュされたblogsを使用して1回のSELECTで取得する
code: (sql)
SELECT * FROM users;
SELECT * FROM blogs WHERE user_id IN (1, 2);
キャッシュされているか確認
loaded? メソッドでキャッシュされているかを真偽値で返す
ActiveRecord::Relationに対しては「クエリ結果がロード済みか」を返す
Associationに対しては「関連オブジェクトがロード済みか」を返す
ActiveRecord::Relationに対してではなくオブジェクト単体に対してloaded? を呼ぶことに注意
code: (rb)
user = User.includes(:blogs).first
user.blogs.loaded? # オブジェクト単体に対して呼ぶ
=> true/false
# 以下だとNG
users = User.includes(:blogs)
users.blogs.loaded?
=> No Method Error
# Rails5以降では以下も可能
user.association(:blogs).loaded
=> true/false
関連の関連をeager_loadingする
includes の引数にネスト構造を表現した関連を記述することで関連の関連をeager_loadingできる
code: (rb)
class User < ApplicationRecord
has_many :blogs
end
class Blog < ApplicationRecord
belongs_to :user
has_one :category
end
class Category < ApplicationRecord
belongs_to :blog
end
userからcategoryをeager_loadingで取得
code: (rb)
# User から Blog を経由して Category を eager_loading
users = User.includes(blogs: :category).all
users.each do |user|
user.blogs.each do |blog|
end
end
内部的にはネストしたハッシュとして解釈される
code: (rb)
User.includes(blogs: :category)
# => { blogs: :category }
# 以下のようなイメージ
{
blogs: {
category: { name: 'カテゴリ名'}
}
}