【Rails】includesとjoinsの違いはN+1の解決の有無だけだと思っていたが・・・という話

# 概要
タイトルの通り、「**includesはN+1を解決してくれるjoinsのようなもの**」という認識だったが、
実はそんな単純な話では無かった・・・という話。
（そんな事くらい、あらかじめ理解しておけって言われそうだけども）

# 状況説明

- **Project**
- **Genre**
- **User**

という3つのモデルがあり、
`ProjectとGenre`、`UserとGenre`にそれぞれ中間テーブルを設けています。

用途は、「この案件（project）の種類(genre)は"AとB"として登録」
「このユーザー(user)は自分の種類(genre)を登録し、該当するものを対応」みたいな使われ方を想定している。


<img width="726" alt="スクリーンショット 2019-05-31 12.49.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251648/46f8ffd3-0600-a8c5-b8b1-889145a825ff.png">
<img width="724" alt="スクリーンショット 2019-05-31 12.50.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251648/20c1d35a-f3d7-beab-c96e-a36a2778b1c7.png">

```project.rb
class Project < ApplicationRecord
  has_and_belongs_to_many :genres


end
```

```genre.rb
class Genre < ApplicationRecord
  has_and_belongs_to_many :users
  has_and_belongs_to_many :projects
end
```

```user.rb
class User < ApplicationRecord
  has_and_belongs_to_many :genres


```

## 期待していた動作
ここで、projectsの案件1〜6のgenreは以下であるとし、

|    :name   |    :genres  |
|:----------:|:----------: |
|案件1       |A,B,C|
|案件2       |B,C,D|
|案件3       |A|
|案件4       |C|
|案件5       |C,E|
|案件6       |D,E|

current_userのgenresが**"A"と"B"と"C"**の場合（`current_user.genres.pluck(:name)  #=> ["A", "B", "C"]`）、

current_userのproject一覧で以下のような一覧が表示される事を期待していた。

↓<<期待する表示>>
<img width="383" alt="スクリーンショット 2019-05-31 12.36.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251648/b479300f-9194-5ab5-e867-e6a8fb396e19.png">


つまり、「**自分の属するgenreが含まれる案件(project)が一覧表示**」され、**ジャンル列には各案件の属するgenreが表示される**ことを期待する。
（↑案件6はDとEなので、表示されない）


## 実装したコードを簡単に説明

ProjectからUserまでの関係はそれぞれの中間テーブルを挟みながら

```
Project -- Genre -- User
```
数珠つなぎであることと、`N+1問題`を考慮し、project.rb内で`includes`を用いて以下のscopeを作成した。

```project.rb
  scope :search_by_user, ->(user) {
    includes(genres: :users).
    where('users.id' => user.id).distinct
  }
```

controllerのindexで、↑の`:search_by_user` scopeを使用。

```projects_controller.rb
  def index
    @projects = Project.search_by_user(current_user)
  end
```

viewは↓（一部）

```index.html.erb
      <% @projects.each do |project| %>
        <tr>
          <td><%= project.name %></t>
          <td><%= project.created_at.to_date.to_s.gsub(/-/, ".") %></td>
          <td><%= project.genres.pluck(:name).join(" , ") %></td>
```

## 意外な結果に...
期待と外れて↓が表示されてしまった。

<img width="391" alt="スクリーンショット 2019-05-31 13.44.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251648/d48e862d-89e8-49d6-0ae9-b0ce463e94d0.png">

ちなみに、current_userの属するgenreを**"A"と"B"**にすると以下のようになる。↓
<img width="390" alt="スクリーンショット 2019-05-31 13.55.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251648/0827a148-3b96-970e-6e5e-45e471c8778f.png">

各案件のジャンル列で、**自分が属するジャンル**のものしか表示されなくなってしまった。

##joinsで解決

**`:scope`** の`include` を `joins`に替えると期待通りの表示となった。

```project.rb
  scope :search_by_user, ->(user) {
    joins(genres: :users).
    where('users.id' => user.id).distinct
  }
```
↓
<img width="383" alt="スクリーンショット 2019-05-31 12.36.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251648/64cad100-1ef4-77f4-0e30-b4a6930f736a.png">


# しかし・・・
**includes**で`N+1問題`が解決されていたところ、**joins**に替えてしまったので、
SQLクエリが`N+1`の状態となってしまった。

表示が期待通りとなり、N+1問題も解決する方法は無いものだろうか・・・