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
に入れておいても便利かな、とちらっと思いました。