Railsにある.pluckメソッドを使っていたところ、本来必要ないN+1クエリを発生させてしまいました。
pluckメソッドとは
ご存じの方も多いかもしれませんが、.pluckメソッドはActiveRecord::Relation(whereなどをかけていった結果のクラス)にあって、リレーションの条件で特定のカラムを取ってくる、というものです。
ぱっと聞けば、ふつうに.map(&:column)とするのとそんなに変わらないようにも思えます。
メリット
この.pluckのメリットは、「モデルの生成をせず、直接特定の列だけを取ってくるSQLを投げる」ということです。余計な列の取得やActiveRecordオブジェクトの生成をすっ飛ばして、値だけを取ってくることができます。
デメリット
ところが、この「ActiveRecordオブジェクトを介さない」 ということは、時としてデメリットに早変わりしてしまいます。というのも、モデル取得時にEager Loadをかけていた場合も、そちらには目もくれず、改めてSQLを投げてしまうのです。リレーションで複数オブジェクトを取得しているときに、各オブジェクトのhas_manyに対してpluckしてしまえば、あっという間にN+1クエリの発生です。
なんとかしてみる
pluckではeager loadを無視してしまう、mapでは不要な場合にも全列取得・ActiveRecord生成をしてしまう、と都合の合わない両者ですが、loaded?でロード状況を見れば、自動で都合の良いほうに切り替えることも可能です。
rel = SomeModel.some_scope(:arg)
rel.loaded? ? rel.map(&:column) : rel.pluck(:column)
.mapluckとでも名前を付けて、ActiveRecord::Relationに入れておいても便利かな、とちらっと思いました。