質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby on Rails 5

Ruby on Rails 5は、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Q&A

解決済

2回答

1555閲覧

【Rails】joinsでN+1問題を解決できないか

YRails

総合スコア13

Ruby on Rails 5

Ruby on Rails 5は、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

0グッド

0クリップ

投稿2019/06/04 12:26

編集2019/06/04 12:29

やりたいこと

  • Project(案件)
  • Genre(ジャンル)
  • User(ユーザー)

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

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

エンジニア向けの求人サイトをイメージして貰えると分かりやすいかと思います。
たとえば、あるユーザーが自分の扱える言語(属するジャンル)をあらかじめ登録しておき(例えばRubyと登録しておく)、
ある求人案件のジャンルが[Ruby,React,AWS,CircleCI,,,]となっていれば、
自分が属するRubyがあるので条件とマッチしている。というような事を実現したいです。

問題点

一度Qiita記事にも書いてあるものがあるので、詳細はこちらを見て頂くと助かるのですが,
「条件がマッチする案件」の一覧を表示させる時、N+1問題を解決させる為に

project.rb

1 scope :search_by_user, ->(user) { 2 includes(genres: :users). 3 where('users.id' => user.id).distinct 4 }

というscopeをproject.rbで作成したのですが、
各案件のジャンル列で、自分が属するジャンルのものしか表示されないという動作となってしまいました。↓


current_userが**"A","B","C"**のGenreに属している場合、

イメージ説明
↑このように表示されて欲しいのですが、

イメージ説明
↑このように自分が属していないGenreがジャンル列から消えてしまいます。

解決したいこと

このscopeのincludesjoinsにすることによって期待の表示がされるようにはなったのですが、
今度はN+1のSQLクエリをするようになってしまいました。

そこで、解決したい課題は、

`joins`を使ってN+1問題が解決できないか。 もしくは`includes`のままで期待通りの表示がされる方法があるか。

です。よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

includes はwhereにその対象が含まれていたら eager_load を使いそうでなければ preload を使う挙動です
便利ですが求めるSQLを意識してそれらを使い分けるのも大事だと思います

eager_laod は join して本体と関連データを一気に取得する(where条件の影響を受ける)
preload は別のSQLで関連データを一気に取得する
joins & preload が条件と事前読み込みを分けたい時

ruby

1joins(genres: :users) # where用の結合 2 .where('users.id' => user.id) 3 .preload(:genres) # N+1用の事前読み込み 4 .distinct

■ 追加の改善

そのテーブルがリレーションに存在していることが前提(今回で言えば joins(:genres) )ですが
merge を使うとリレーションの結合ができるため記述を短くできます
また User まで join しなくて良くなるので SQL もシンプルになります

ruby

1joins(:genres).merge(user.genres).preload(:genres).distinct

投稿2019/06/05 07:00

Ighrs

総合スコア656

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YRails

2019/06/06 03:00

ご回答ありがとうございます。 回答頂いたメソッドチェーンにて仰る通りの動作が確認出来ました。 大変勉強になりました。
guest

0

includes と joins と両方使って下さい。
includes は 一緒にとり込むtableを指定し、N+1 に関わります
joins は 検索時の関連tableを指定します。

質問の内容ですと、joins ではなく left_outer_joins かな

投稿2019/06/04 20:38

winterboum

総合スコア23340

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YRails

2019/06/06 03:02

ご回答ありがとうございます。 大変勉強になります。 差し支えなければで構わないのですが、includesとjoins両方使うパターンのcodeを教えて頂けませんでしょうか??
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問