joinsを使ってeager loadingをする方法

joinsを使ってeager loadingをする方法です。
その前提の話として

+ N + 1 問題
+ eager lodingとは
+ includesの欠点
+ joins

と長くなってしまいましたが、そんなん知ってるという方は最後の

+ joinsを使ってeager loadingをする方法

そこだけ見てください。

# N + 1 問題

```ruby
class Guild < ActiveRecord::Base
  has_many :users
end

class User < ActiveRecord::Base
  belongs_to :guild
end
```

というモデルがあった時

```ruby
users = User.all
users.each { |user| puts user.guild.name }
```

みたいなことをした時に(適当ですが)

```mysql
SELECT `users`.* FROM `users`
SELECT `guilds`.* FROM `guilds` WHERE `guilds`.`id` = 1 LIMIT 1
SELECT `guilds`.* FROM `guilds` WHERE `guilds`.`id` = 2 LIMIT 1
SELECT `guilds`.* FROM `guilds` WHERE `guilds`.`id` = 3 LIMIT 1
```

`users.all`で一回。
`users.each { |user| puts user.guild.name }`で`users`の数の分だけSQLが発行されます。
これが N + 1 問題です。この問題を回避するのが eager lodingです

# eager loadingとは

```ruby
users = User.includes(:guild)
```

上記は以下のSQLを発行されます。

```mysql
SELECT `users`.* FROM `users`
SELECT `guilds`.* FROM `guilds` WHERE `guilds`.`id` IN (1, 2, 3)
```

`includes`を使用することで2つのSQLを発行し、`users`と`guilds`の両方を一気に読み込みます。
そのため、`users.each { |user| puts user.guild.name }`では一度もSQLを発行することなく処理ができます。
N + 1だったクエリ数が2回になります。この仕組みがeager loadingです。

# includesの欠点

eager loadingはパフォーマンス向上のために必要です。
しかし、`includes`には問題があります。

```ruby
User.includes(:guild).where("guilds.id IN (1, 2, 3)")
```

というように`where`句で`includes`で読み込んだテーブルを条件にしようとしたとき

```mysql
SELECT `users`.`id` AS t0_r0, `users`.`guild_id` AS t0_r1, `users`.`name` AS t0_r2, `guilds`.`id` AS t1_r0, `guilds`.`name` AS t1_r1 
FROM `users` 
LEFT OUTER JOIN `guilds` ON `guilds`.`id` = `users`.`guild_id` 
WHERE (guilds.id IN (1, 2, 3))
```

というSQLになります。発行されるのは`LEFT JOIN`なので`guilds`が存在しない時でも`users`は全部ロードされます。
意図通りであれば問題なのですが、基本的にはこういう場合`joins`を使うことが推奨されています。

### rails4 

rails4では`references`を明示的に指定するようになってます。(らしい)
http://qiita.com/joker1007/items/e951d0cf98309dba90db

```ruby
User.includes(:guild).where("guilds.id IN (1, 2, 3)").references(:guilds)
```

# joins

http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations
関連するテーブルを読み込み`where`句で関連テーブルを条件にするときは`includes`ではなく`joins`が推奨されています。

```ruby
User.joins(:guild).where("guilds.id IN (1, 2, 3)")
```

```mysql
SELECT `users`.* FROM `users` INNER JOIN `guilds` ON `guilds`.`id` = `users`.`guild_id` WHERE (guilds.id = 1,)
```

こちらは`INNER JOIN`なので先ほどの`includes`の問題がありせん。
しかし、eager loadingが効いていないのです。
`users.each { |user| puts user.guild.name }`を実行した時に

```mysql
SELECT `guilds`.* FROM `guilds` WHERE `guilds`.`id` = 1 LIMIT 1
SELECT `guilds`.* FROM `guilds` WHERE `guilds`.`id` = 2 LIMIT 1
SELECT `guilds`.* FROM `guilds` WHERE `guilds`.`id` = 3 LIMIT 1
```

というSQLが発行されてしまい、N + 1問題が起きます。


# joinsを使ってeager loadingをする方法

そこで、joinsを使ってeager loadingをする方法です。

`select`を使います。

```ruby
User.joins(:guild)
  .where("guilds.id IN (1, 2, 3)")
  .select("users.*, guilds.name AS hoge")
```

次のSQLが発行されます。

```mysql
SELECT users.*, guilds.name AS hoge FROM `users` 
INNER JOIN `guilds` ON `guilds`.`id` = `users`.`guild_id` 
WHERE (guilds.id IN (1, 2, 3))
```

もちろんですが、`SELECT users.*, guilds.name AS hoge`となり、`users.*`と`guilds.name`がロードされます。
ロードしておきたい`guilds`のカラムがたくさんあればその分`select`の中に書く必要があります。

ロードしたデータには`user.guild.hoge`や`user.guild.name`ではなく`user.hoge`という風にアクセスします。

```ruby
users.each { |user| puts user.hoge }
```

`user.guild.hoge`はもちろんエラーになります。
`user.guild.name`では`guild`のロードが実行されてしまってN + 1問題になってしまいます。