読者です 読者をやめる 読者になる 読者になる

activerecordの関連するモデルをまとめて取得するメソッドのincludesが多段の関連モデルでも使えた

例えば、以下のような3層にまたがる関連モデルがあるとすると、

class Team
  has_many :members
end

class Member
  belongs_to :team
  has_many :tasks
end

class Task
  belongs_to :member
end

この場合、Taskのインスタンスオブジェクトから、
関連するTeamオブジェクトの情報を呼び出す場合、

task = Task.last
task.member.team.name
=> "A team"

こうなって、SQL的には、

SELECT `tasks`.* FROM `tasks` LIMIT 1
SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

こうなります。
一見問題ないようにも見えますが、

仮にTaskオブジェクトを

Task.all

のように取得してeach等でループする場合、
Taskオブジェクトの数だけ、membersテーブルとteamsテーブルにアクセスしてしまいます。

tasks = Task.limit 10
tasks.each {|task| p task.member.team.name }
SELECT `tasks`.* FROM `tasks` LIMIT 10
SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 4 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 4 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 4 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 4 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 4 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 4 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 3 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 3 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 2 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 2 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 4 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 4 LIMIT 1

そこで登場するのが関連するモデルをまとめて取得するメソッドのincludesです。
includesメソッドを多段の関連モデルで呼び出す場合は、以下のように記述します。

tasks = Task.limit(10).includes(:member => :team)
tasks.each {|task| p task.member.team.name }
SELECT `tasks`.* FROM `tasks` LIMIT 10
SELECT `members`.* FROM `members` WHERE `members`.`id` IN (6, 4, 3, 2)
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` IN (5, 4, 3, 2)