すでにメンバーの場合は

無料会員登録

GitHubアカウントで登録 Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

登録がまだの方はこちらから

Pikawakaにログイン

GitHubアカウントでログイン Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

Rails

【Rails】 N+1問題をincludesメソッドで解決しよう!

ぴっかちゃん
ぴっかちゃん

includesメソッドとは、アソシエーションの関連付けを事前に取得してN +1問題を解決してくれるメソッドです。これによって、指定された関連付けが最小限のクエリ回数で読み込まれます。

includesメソッドの基本構文
1
モデル名.includes(:関連名) # 関連名はテーブル名ではない

includesメソッドに渡す引数は、テーブル名ではなくアソシエーションで定義した関連名を指定します。それでは、includesメソッドの使い方とそもそもN+1問題とは何なのかを解説していきます。

includesメソッドの基礎知識

この章では、includesメソッドの基礎知識について解説します。

N + 1問題とは?

N + 1問題とは、必要以上にSQLが発行されてしまい、動作が悪くなってしまう問題のことです。

SQLとは、データベース言語の1つでデータベースの操作を行います。RailsではActive Recoredによってデータベース操作を行う事が出来ますが、裏側ではSQLが実行されています。

SQLが実行されている例
1
 Owner Load (2.7ms)  SELECT `owners`.* FROM `owners`

「Owner Load (2.7ms)」の様にSQLを実行する際にわずかですが時間が掛かります。これが数回でしたら特に動作に影響はありませんが、何万回と回数が増えてしまうと動作が悪くなってしまいます。

N + 1 問題をコードで確認する

ネコの飼い主のテーブルがownersテーブル、ネコの名前を扱うテーブルがcatsテーブルの2つのテーブルを例にN +1 問題を確認していきましょう。テーブルの構成は下記の通りです。

ownersテーブルとcatsテーブル

catsテーブルのowner_idカラムが外部キーとなって対応するownersテーブルのレコードを参照しています。主キーと外部キーが分からない方は、まずはこちらの「図とテーブルで理解するアソシエーション」を参考にしてください。

「1人のownerは複数のcatsを持つ関係(Owner has_many cats)」なので、Ownerモデルにhas_manyメソッドを定義し、Catモデルにはbelongs_toメソッドを定義します。

OwnerモデルとCatモデルにアソシエーション定義
1
2
3
4
5
6
7
8
9
# owner.rb
class Owner < ActiveRecord::Base
    has_many :cats
end

# cat.rb
class Cat < ActiveRecord::Base
    belongs_to :owner
end

「全ての飼い主の猫の一覧」をviewで表示したい場合に、controller側で全ての飼い主をallメソッドで取得し、view側で飼い主の持つcatsをアソシエーションによって下記の様に記述する事が出来ます。

N+1問題が起きてしまうコードを確認
1
2
3
4
5
6
7
8
9
# controller側
@owners = Owner.all

# view側
@owners.each do |owner|
  owner.cats.each do |cat|
    cat.name
  end
end

このコードを実行してしまうと、必要以上にSQLを発行するN+1問題が起こります。では、実際にコードを実行した時のテーブルとSQLの流れを確認していきましょう。

N + 1 問題をテーブルとSQLで確認する

まずは「@owners = Owner.all」のコードを確認していきます。このコードの裏側で発行されているSQL文に注目して下さい。

controller側 | N+1問題が起きてしまうコードを確認
1
2
@owners = Owner.all
SELECT `owners`.* FROM `owners` #発行されるSQL文

「Owner.all」のコードが実行されると、下記の様に「ownersテーブルからownersテーブルの全てのカラム」が取得されていることが分かります。このSQLによって、ownersテーブルに1回のアクセスが行われました。

Owner.allによって取得されるオブジェクト

次にveiw側のコードを確認しましょう。SQLをみると、catsテーブルに対して、4回のアクセスが行われている事がわかります。この4回のアクセスが行われている流れを確認していきます。

view側 | N+1問題が起きてしまうコードを確認
1
2
3
4
5
6
7
8
9
10
11
@owners.each do |owner|
  owner.cats.each do |cat| # このコードが4回のSQL文を発行している
    cat.name
  end
end

# 4回SQL文を発行している
SELECT `cats`.* FROM `cats`  WHERE `cats`.`owner_id` = 1
SELECT `cats`.* FROM `cats`  WHERE `cats`.`owner_id` = 2
SELECT `cats`.* FROM `cats`  WHERE `cats`.`owner_id` = 3
SELECT `cats`.* FROM `cats`  WHERE `cats`.`owner_id` = 4

まず、1行目の「@owners.each do |owner|」のeachメソッドが「@owners = Owner.all」で取得した全ての飼い主のレコードから1つずつレコードを取り出してownerに代入しています。

@owners.each do |owner|の流れ

最初のownerには、田中さんのレコードが入っているので「owner.cats.each do |cat|」のowner.catsで田中さんの飼っている猫のレコードを全て取得します。これが1回目のSQLの発行で、ownersテーブルに関連するcatsテーブルに1回目のアクセスが行われました。

田中さんの飼い猫のレコードを取得

田中さんのレコードの処理が終わると、次のownerには、ownersテーブルのid=2の伊藤さんのレコードが入るので「owner.cats.each do |cat|」のowner.catsで伊藤さんの飼っている猫のレコードを全て取得します。これでownersテーブルに関連するcatsテーブルに2回目のアクセスが行われました。

ownersテーブルのid=2の伊藤さんの飼い猫のレコードを全て取得

そして、次にownersテーブルのid=3の高橋さんのレコードがownerに入り、高橋さんの飼い猫を全て取得します。これでownersテーブルに関連するcatsテーブルに3回目のアクセスが行われました。

ownersテーブルのid=3の高橋さんの飼い猫のレコードを全て取得

最後にownersテーブルのid=4の加藤さんの飼い猫のレコードを見にいきます。加藤さんは猫を飼っていないのでレコードは存在しませんが、SQLは発行されています。これでownersテーブルに関連するcatsテーブルに4回目のアクセスが行われました。

加藤さんの飼い猫を読み込むSQL

SQLが「ownersテーブルへのアクセスが1回 」に対して「catsテーブルへのアクセスがownersテーブルのレコードの数(4回)」発行されています。

このようにownersテーブルへのアクセス1回に対して、関連するテーブルがN回発行されている1+Nの状況を「N+1問題」と言います。

N+1問題

ownersテーブルにアクセスする際に、関連するcatsテーブルのレコードを取得する事が出来れば、発行されるSQLも4回から1回にまとめる事が出来ます。

これを実現してくれるのがincludesメソッドなのです。

ぴっかちゃん

N+1問題については、こちらのRuby on Railsの良書として有名な書籍でも触れられているのでぜひ参考にしてみてください。

includesメソッド

includesメソッドは、引数にアソシエーションで定義した関連名を指定して定義します。(※テーブル名ではないので注意してください)

includesメソッドの定義
1
モデル名.includes(:関連名) # 関連名はテーブル名ではない

includesメソッドで指定された関連付けが最小限のクエリ回数で読み込まれます。これによってN+1問題を解決する事が出来ます。それでは、先ほどのサンプルコードをincludesメソッドを使って確認していきます。

includesメソッドを使ってN+1問題を解決しよう

ownersテーブルにアクセスする際に、関連するcatsテーブルをincludesメソッドを使って取得してみましょう。

Ownerモデルに定義されていたアソシエーションは下記の通りでした。

owner.rb|Ownerモデルのアソシエーション
1
2
3
class Owner < ActiveRecord::Base
    has_many :cats #これが関連名
end

「Owner has many cats」の関係なので、includes(:関連名)には、includes(:cats)が入ります。

@owners = Owner.allで取得していた箇所を@owners = Owner.includes(:cats)に変更します。

controller側 | includesメソッドでN+1問題を解決する
1
2
3
4
5
6
7
8
9
10
11
12
@owners = Owner.includes(:cats) # Owner.allから変更

 # 発行される2つのSQL
SELECT `owners`.* FROM `owners` 
SELECT `cats`.* FROM `cats`  WHERE `cats`.`owner_id` IN (1, 2, 3, 4)

# 返り値
=> #<ActiveRecord::Relation
[#<Owner id: 1, name: "田中", created_at: "2019-11-25 08:51:22", updated_at: "2019-11-25 08:51:22">, 
#<Owner id: 2, name: "伊藤", created_at: "2019-11-25 08:51:27", updated_at: "2019-11-25 08:51:27">, 
#<Owner id: 3, name: "高橋", created_at: "2019-11-25 08:51:32", updated_at: "2019-11-25 08:51:32">, 
#<Owner id: 4, name: "加藤", created_at: "2019-11-25 09:54:50", updated_at: "2019-11-25 09:54:50">]>

「Owner.includes(:cats)」のコードを実行する事によって、下記の2つのSQLが発行されます。1行目は、ownersテーブルの全てのレコードを取得するSQL文です。2行目では、catsテーブルからWHERE句で指定した条件にマッチするレコードを取得しています。

includesメソッドで発行されるSQL
1
2
SELECT `owners`.* FROM `owners` 
SELECT `cats`.* FROM `cats`  WHERE `cats`.`owner_id` IN (1, 2, 3, 4)

WHERE句では、catsテーブルのowner_idカラムの値がIN句で指定している1,2,3,4のいずれかとマッチするレコードを取得しています。このIN句の1,2,3,4の値は、1行目で取得したownersテーブルの主キーの値が入っています。

Owner.includes(:cats)で発行される2つのSQL文

includesメソッドを使わない場合は、関連するowner_idカラムの値を1つずつ指定して取得していたのでcatsテーブルに4回のアクセスが必要でしたが、IN句でカラムの値をまとめて指定した事によって1回のアクセスで取得出来るようになりました。

includesメソッドを使った事でcatsテーブルのアクセスが1回になる

そして、includesメソッドを使った場合の返り値にも注目してください。取得されているのは、ownersテーブルのレコードのみに見えますが、内部的にはownersテーブルに関連するcatsテーブルのレコードも含めて取得する事が出来ています。

controller側 | includesメソッドを使った場合の返り値
1
2
3
4
5
6
7
8
@owners = Owner.includes(:cats) # Owner.allから変更

# 返り値
=> #<ActiveRecord::Relation 
[#<Owner id: 1, name: "田中", created_at: "2019-11-25 08:51:22", updated_at: "2019-11-25 08:51:22">,
#<Owner id: 2, name: "伊藤", created_at: "2019-11-25 08:51:27", updated_at: "2019-11-25 08:51:27">, 
#<Owner id: 3, name: "高橋", created_at: "2019-11-25 08:51:32", updated_at: "2019-11-25 08:51:32">, 
#<Owner id: 4, name: "加藤", created_at: "2019-11-25 09:54:50", updated_at: "2019-11-25 09:54:50">]>

この状態で、view側のコードを実行してみます。

view側 | includesメソッドを使った場合
1
2
3
4
5
6
7
8
9
10
11
@owners.each do |owner|
  owner.cats.each do |cat| 
    puts cat.name
  end
end

# コード実行結果
モモ
ミー
クロ
ハナ

特にSQLが発行される事なくコードが実行されます。これは、controller側でincludesメソッドを使って既にownersテーブルのレコードに関連するcatsテーブルのレコードが取得されている為、view側で必要以上のSQLを発行せずに済みます。

このように、includesメソッドを使って関連するテーブルのレコードをまとめて取得させる事によって必要以上のSQLを発行する事なく済みます。

Catモデルにincudesメソッドを使って関連するモデルも取得しよう

先ほどは、includesメソッドを使ってownersテーブルにアクセスする際に関連するcatsテーブルのレコードを事前に取得してN+1問題を解決することが出来ていましたが、catsテーブルのレコードを全て取得して関連するownersテーブルのレコードを取得する場合も、includesメソッドを利用しないとN+1問題が起こります。

N+1問題が発生する例を見ながら確認していきます。controller側にallメソッドを利用してcatsテーブルから全てのレコードを取得します。この時にcatsテーブルに対して1回のアクセスが行われます。

contoller側 | catsテーブルに対して1回のアクセスが行われる -->
1
2
@cats = Cat.all
SELECT `cats`.* FROM `cats` 

そして、catsテーブルの全てのレコードが入っている@catsをeachメソッドで1つ1つ取り出し、猫の飼い主の名前を「cat.owner.name」で指定します。この時にN+1問題がおこります。

view側 | catsテーブルに対して1回のアクセスが行われる
1
2
3
4
5
6
7
8
9
@cats.each do |cat|
  cat.owner.name # ここでN+1問題が発生する
end

# ownersテーブルに対して4回アクセスされてしまう
SELECT  `owners`.* FROM `owners`  WHERE `owners`.`id` = 2 LIMIT 1
SELECT  `owners`.* FROM `owners`  WHERE `owners`.`id` = 1 LIMIT 1
SELECT  `owners`.* FROM `owners`  WHERE `owners`.`id` = 3 LIMIT 1
SELECT  `owners`.* FROM `owners`  WHERE `owners`.`id` = 1 LIMIT 1

「cat.owner.name」で発行されるSQLを確認すると、catsテーブルのアクセスが1回だったのに対して、関連するownersテーブルに対してのアクセスは、4回行われています。

catsテーブルに関連するownersテーブルへのアクセスが4回行われている例

includesメソッドを使って、catsテーブルのレコードを取得する際に関連するownersテーブルのレコードを事前に取得することで、このN+1問題を解決することが出来ます。

まず、Catモデルに定義されていたアソシエーションは下記の通りでした。

cat.rb|Catモデルのアソシエーション -->
1
2
3
class Cat < ActiveRecord::Base
    belongs_to :owner #これが関連名
end

「Cat belongs to Owner」の関係になので、includes(:関連名)の関連名には「owner」を指定して、Cat.includes(:owner)になります。

controller側 | includesメソッドでN+1問題を解決する -->
1
2
3
4
5
6
7
8
9
10
11
12
@cats = Cat.includes(:owner)

 # 発行される2つのSQL
SELECT `cats`.* FROM `cats`
SELECT `owners`.* FROM `owners`  WHERE `owners`.`id` IN (2, 1, 3)

# 返り値
=> #<ActiveRecord::Relation
[#<Cat id: 1, name: "クロ", owner_id: 2, created_at: "2019-11-25 08:53:31", updated_at: "2019-11-25 08:53:31">, 
#<Cat id: 2, name: "モモ", owner_id: 1, created_at: "2019-11-25 08:53:43", updated_at: "2019-11-25 08:53:43">, 
#<Cat id: 3, name: "ハナ", owner_id: 3, created_at: "2019-11-25 08:53:59", updated_at: "2019-11-25 08:53:59">, 
#<Cat id: 4, name: "ミー", owner_id: 1, created_at: "2019-11-25 08:54:39", updated_at: "2019-11-25 08:54:39">]>

Cat.includes(:owner)」のコードを実行する事によって、下記の2つのSQLが発行されます。1行目は、catsテーブルの全てのレコードを取得するSQL文です。2行目では、ownersテーブルからWHERE句で指定した条件にマッチするレコードを取得しています。

includesメソッドで発行されるSQL
1
2
SELECT `cats`.* FROM `cats`
SELECT `owners`.* FROM `owners`  WHERE `owners`.`id` IN (2, 1, 3)

WHERE句では、ownersテーブルのidカラムの値がIN句で指定している2,1,3のいずれかとマッチするレコードを取得します。このIN句の2,1,3の値は、1行目で取得したcatsテーブルのcat_idの外部キーの値になります。

Cat.includes(:owner)で発行されるSQL

IN句によってカラムで値を事前にまとめて取得しているので、ownersテーブルにアクセスしていた回数を4回から1回にまとめることが出来ました。

includesメソッドを使った場合

取得できる様になりN+1問題を解決することが出来ました。includesメソッドを使って事前にcatsテーブルのレコードと関連するownersテーブルのレコードが入った@catsを使いview側のコードを実行します。

view側 | includesメソッドを使用した場合-->
1
2
3
4
5
6
7
8
9
@cats.each do |cat|
  cat.owner.name
end

# コード実行結果
伊藤
田中
高橋
田中

事前にcatsテーブルに関連するownersテーブルのレコードを取得することが出来ているので、必要のないSQLは発行されずN+1問題を解決することが出来ました。

includesメソッドの応用的な使い方

この章では、includesメソッドの応用的な使い方について解説します。

includesメソッドのネスト

1人の飼い主は複数の猫を飼っていましたが、その猫には複数の子猫がいます。子猫のテーブルをchildrenテーブルとします。1匹の猫は複数の子猫を持ち「Cat has many Child」の関係になります。catsテーブルとchildrenテーブルの関係は下記の通りです。

catsテーブルとchildrenテーブルの関係

さらに複数の猫は1人の飼い主に属しているので、ownersテーブルを含めた3つのテーブルの関係を整理すると下記の様になります。

catsテーブル、ownersテーブル、childrenテーブルの関係性

ownersテーブルとchildrenテーブルは、直接の関係はありませんが、childrenテーブルが外部キー(cat_id)を通してcatsテーブルを参照し、catsテーブルが外部キー(owner_id)を通してownersテーブルを参照しています。

それぞれの関係性をアソシエーションで定義すると、下記の様になります。

OwnerモデルとCatモデル,Childモデルにアソシエーション定義 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# owner.rb
class Owner < ActiveRecord::Base
    has_many :cats
end

# cat.rb
class Cat < ActiveRecord::Base
    belongs_to :owner
    has_many :children
end

# child.rb
class Child < ActiveRecord::Base
    belongs_to :cat
end

先ほどのサンプルコードでは、includesメソッドを使って飼い主に関連する猫のレコードを取得する事によってN+1問題を解決する事が出来ていました。

controller側 | includesメソッドを使った場合のサンプルコード -->
1
2
3
4
5
@owners = Owner.includes(:cats)

 # 発行される2つのSQL
SELECT `owners`.* FROM `owners` 
SELECT `cats`.* FROM `cats`  WHERE `cats`.`owner_id` IN (1, 2, 3, 4)
view側 | includesメソッドを使ったサンプルコード -->
1
2
3
4
5
6
7
8
9
10
11
@owners.each do |owner|
  owner.cats.each do |cat| 
    puts cat.name
  end
end

# コード実行結果
モモ
ミー
クロ
ハナ

しかし、今回の様なテーブルがネストしている状態でcatsテーブルのレコードに関連するchildrenのレコードを取得する時は、どの様なSQLが発行されるのでしょうか? view側のコードを変更して確認していきます。

飼い主の猫の子供を取得してみよう

飼い主の猫の子供を取得するには、飼い主に関連する猫、更にその猫に関連する子供のレコードを取得する必要があります。

飼い主に関連する猫に関連する子供のレコード

その為「cat.children.each do |child|」のコードを下記の様にview側に追加します。このコードによって発行されているSQLを確認すると、childrenテーブルに対して4回のアクセスが行われいる事が分かります。

view側 | コードを追加 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
@owners.each do |owner|
  owner.cats.each do |cat| # includesメソッドで取得しているのでN+1問題は起こらない 
    cat.children.each do |child|  # ここでN+1問題が起こる
      puts child.name
    end
  end
end

# childrenテーブルに対して4回アクセスしている
SELECT `children`.* FROM `children`  WHERE `children`.`cat_id` = 2
SELECT `children`.* FROM `children`  WHERE `children`.`cat_id` = 4
SELECT `children`.* FROM `children`  WHERE `children`.`cat_id` = 1
SELECT `children`.* FROM `children`  WHERE `children`.`cat_id` = 3

どこかで見たSQLと似ていますね。これは、includesメソッドを使用していない時の「owner.cats.each do |cat| 」と一緒です。今回はcontroller側でincludesメソッドを使って関連するcatsテーブルのレコードを事前に取得しているのでN+1問題が起こりません。

しかし、catsテーブルのレコードと関連するchildrenテーブルのレコードは、事前に取得していないので、catsテーブルのレコードの回数だけchildrenテーブルにSQLが発行される事になります。

childrenテーブルに対してSQLが4回発行される

catsテーブルへのアクセスは、includesメソッドによって1回のアクセスだけになっています。SQLが「catsテーブルへのアクセスが1回 」に対して「 childrenテーブルへのアクセスがcatsテーブルのレコードの数(4回)発行」されるN+1問題が起きてしまっています。

childrenテーブルにN+1問題が起きている

連続して参照する様な関連の場合もincludesメソッドを使って解決する事が出来ます。

ネストしている状態のN+1問題もincludesメソッドで解決しよう

連続して参照している関連名がある場合に、includesメソッドは下記の様に定義します。

includesメソッドのネストの書き方-->
1
モデル名.includes(関連名1: :関連名2)

それでは、 先ほどのownersテーブルとcatsテーブル、childrenテーブルを例に確認してみましょう。

1匹の猫は複数の子猫を持つので「Cat has many Child」の関係なので、それぞれのアソシエーションは下記の様に定義します。

OwnerモデルとCatモデル,Childモデルにアソシエーション定義 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# owner.rb
class Owner < ActiveRecord::Base
    has_many :cats
end

# cat.rb
class Cat < ActiveRecord::Base
    belongs_to :owner
    has_many :children
end

# child.rb
class Child < ActiveRecord::Base
    belongs_to :cat
end

そして、このアソシエーションを元に連続して参照している関連をincludesメソッドで指定します。includesメソッドの引数には、「モデル名.joins(Ownerモデルの関連名: :Catモデルの関連名)」つまりOwner.includes(cats: :children)を指定します。

includesメソッドのネストの書き方-->
1
2
3
4
5
6
7
8
9
10
11
12
13
@owners = Owner.includes(cats: :children) #includes(Ownerモデルの関連名: :Catモデルの関連名)

# 発行される3つのSQL
SELECT `owners`.* FROM `owners`
SELECT `cats`.* FROM `cats`  WHERE `cats`.`owner_id` IN (1, 2, 3, 4)
SELECT `children`.* FROM `children`  WHERE `children`.`cat_id` IN (1, 2, 3, 4)

# 返り値
=> #<ActiveRecord::Relation
[#<Owner id: 1, name: "田中", created_at: "2019-11-25 08:51:22", updated_at: "2019-11-25 08:51:22">, 
#<Owner id: 2, name: "伊藤", created_at: "2019-11-25 08:51:27", updated_at: "2019-11-25 08:51:27">, 
#<Owner id: 3, name: "高橋", created_at: "2019-11-25 08:51:32", updated_at: "2019-11-25 08:51:32">, 
#<Owner id: 4, name: "加藤", created_at: "2019-11-25 09:54:50", updated_at: "2019-11-25 09:54:50">]>

「Owner.includes(cats: :children)」のコードを実行する事によって、下記の3つのSQLが発行されます。1行目は、ownersテーブルの全てのレコードを取得するSQL文です。2行目では、catsテーブルからWHERE句で指定した条件にマッチするレコードを取得しています。そして3行目のchildrenテーブルですが、IN句を使ってまとめて取得しているので1回のアクセスのみになっています。

Owner.includes(cats: :children)によって発行される3つのSQL
1
2
3
SELECT `owners`.* FROM `owners`
SELECT `cats`.* FROM `cats`  WHERE `cats`.`owner_id` IN (1, 2, 3, 4)
SELECT `children`.* FROM `children`  WHERE `children`.`cat_id` IN (1, 2, 3, 4)

更に返り値に注目すると、取得されているのは、ownersテーブルのレコードのみに見えますが、内部的にはownersテーブルに関連するcatsテーブルのレコードに更に関連するchildrenテーブルのレコードも含めて取得する事が出来ています。

Owner.includes(cats: :children)の返り値 -->
1
2
3
4
5
6
7
8
@owners = Owner.includes(cats: :children) #includes(Ownerモデルの関連名: :Catモデルの関連名)

# 返り値
=> #<ActiveRecord::Relation
[#<Owner id: 1, name: "田中", created_at: "2019-11-25 08:51:22", updated_at: "2019-11-25 08:51:22">, 
#<Owner id: 2, name: "伊藤", created_at: "2019-11-25 08:51:27", updated_at: "2019-11-25 08:51:27">, 
#<Owner id: 3, name: "高橋", created_at: "2019-11-25 08:51:32", updated_at: "2019-11-25 08:51:32">, 
#<Owner id: 4, name: "加藤", created_at: "2019-11-25 09:54:50", updated_at: "2019-11-25 09:54:50">]>

この状態で、view側のコードを実行してみます。

ネストにもincludesメソッドを使った場合 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# controller側
@owners = Owner.includes(cats: :children) #includes(Ownerモデルの関連名: :Catモデルの関連名)

# view側
@owners.each do |owner|
  owner.cats.each do |cat| 
    cat.children.each do |child|
      puts child.name
    end
  end
end

# 実行結果
クロコ
ハナコ

特にSQLが発行される事なくコードが実行されます。これは、controller側でincludesメソッドを使って既にownersテーブルのレコードに関連するcatsテーブルのレコードの更に関連するchildrenのレコードが取得されているからです。

このように、連続して関連する様な関係でもincludesメソッドを使ってまとめて関連するレコードを取得する事によってN+1問題を解決する事が出来ます。

この記事のまとめ

  • includesメソッドとは、アソシエーションの関連付けを事前に取得してN +1問題を解決してくれるメソッドのこと
  • N + 1問題とは、必要以上にSQLが発行されてしまい、動作が悪くなってしまう問題のこと
  • SQLとは、データベース言語の1つでデータベースの操作を行う