u16suzuの blog

u16suzu のブログです。

ARのjoinまわりのメソッドを勉強した

ARのjoinまわりのメソッドを勉強した。N+1を解消するのにとても便利。

joins

  • アソシエーションをキャッシュしない
  • アソシエーションを検索条件でだけつかいたいときに有効
  • 内部的には普通にjoinしてる

preload

  • アソシエーションをキャッシュする
  • 必要なテーブルをまとめて別のselect文で読み込みするのでクエリは2回に発行される
  • あまりjoinしたくないでかいテーブルに有効
  • joinしてないので、絞り込み条件に関連テーブルを使えない
  • index系のAPIでの N+1 の解消に有効

eager_load

  • アソシエーションをキャッシュする
  • joinする
  • joinしているので、絞り込み条件に関連テーブルを使える

includes

  • preload と eager_load を適切に使い分けてくれる
    • join先で絞り込んでいたら eager_load
    • してなかったら preload

merge

  • joinsをチェインすることができる
  • ちょっと初見だとどんなクエリになるか判断つかないかもしれない
Comment.joins(:entry).where(Entry.merge(:draft)) # scopeを使うとき
Comment.joins(:entry).merge(Entry.where(published_at: nil)) # scopeを使わないとき

これらのメソッドを使ってもどうしてもN+1が解決できない場合

最後に、これらのメソッドを使ってもどうしてもN+1が解決できない場合は、

一旦Rubyの配列に確保してしまってからループの中でRubyの方のselect メソッドを使うことで解決した。

# N+1 の発生を防ぐため, あらかじめまとめて読み込んでおく
user_favs = @user.favs.where( target_id: Articles.published.pluck(:id) ).to_a

users = list.map do |elem|
  user_fav = user_favs.select{ |f| f.id == elem.fav_id } # Array#select を使う
  # user_favを使って諸々の処理をする
end

参考

ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い - Qiita