RailsのN+1避けのものもう一つ作った

RailsでFoo.where(condition).n1_safeと付け加えるだけで簡単にN+1クエリを回避出来る奴
https://github.com/tompng/n1_safe

使い方

gem 'n1_safe', github: 'tompng/n1_safe'
@posts = Post.all.includes(:comments,ほげほげ)

みたくincludesする代わりに

@posts = Post.all.n1_safe
@post = Post.find(params[:id]).n1_safe

とつけ加えるだけでN+1が起こらなくなる。

@posts.each{|post|
  post.comments.each{|comment|
    comment.user.id
    comment.favs.count
  }
}

SQLはこんな感じ

Comment Load (0.8ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (1, 2, ... 19, 20)
User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (20, 16, ... 15, 4)
 (0.5ms)  SELECT COUNT(*) AS count_all, target_id AS target_id FROM "favs" WHERE "favs"."target_id" IN (49, 73, ... 55, 67) AND "favs"."target_type" = ? GROUP BY "favs"."target_id"  [["target_type", "Comment"]]

↑可能ならgroup_by/countもやるよ 出来ない場合(has_many throughの場合など)はpreloadしてto_a.sizeで代用


前作ったsmart_jsonは依存関係を定義から読んで先にincludesする方針。
今回は呼ばれて必要になった時にあとからpreloadする方針。
n1_safeを呼んだルートとなるオブジェクトを覚えておいて、
関連モデルを呼び出した時とRelationを配列にする時に、それを起点にpreloadする。

・BasicObject便利
ActiveRecord::Associations::Preloader便利
・バージョン違い辛い
ActiveRecord::Base#reflectionsが無くなってたりreflectionsのkeyがsymbolかstringか違ったり