1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-05-31

概要

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

状況説明

  • Project
  • Genre
  • User

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

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

スクリーンショット 2019-05-31 12.49.57.png スクリーンショット 2019-05-31 12.50.55.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一覧で以下のような一覧が表示される事を期待していた。

↓<<期待する表示>>
スクリーンショット 2019-05-31 12.36.42.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>

意外な結果に...

期待と外れて↓が表示されてしまった。

スクリーンショット 2019-05-31 13.44.24.png

ちなみに、current_userの属するgenreを**"A"と"B"**にすると以下のようになる。↓
スクリーンショット 2019-05-31 13.55.08.png

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

joinsで解決

:scopeincludejoinsに替えると期待通りの表示となった。

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


スクリーンショット 2019-05-31 12.36.42.png

しかし・・・

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

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?