pluck.uniqをdistinct.pluckに置換する

先日リリースした RuboCop Rails 2.6.0 でバグフィックスされた Rails/UniqBeforePluck についての話。

この Cop は RuboCop Rails に細かな部署があるなら、スタイルではなくパフォーマンスに属するもの。RDBMS から結果を取得したあとに Rubyuniq で一意にするのではなく、DISTINCT を使ったクエリを RDBMS に発行してあらかじめ一意になっている結果を得るもの。

デフォルトで以下のような検出を行う。

# bad
Model.pluck(:foo).uniq

# good
Model.distinct.pluck(:foo)

なお、この Rails/UniqBeforePluck cop はバグ修正された RuboCop Rails 2.6 以上でないと distinct に置換すべきところ Rails 5.0 で非推奨にされた uniq にオートコレクトしてしまう問題がある。

github.com

いまメンテナンスされている Rails 5.2, Rails 6.0 なんかでは NoMethodError: undefined methoduniq'` エラーになるので自動修正を使うときは RuboCop Rails を最新の 2.6.0 に更新してください。

なお Rails/UniqBeforePluckEnforcedStyle のオプションを持っていて、デフォルトの EnforcedStyle: conservative偽陰性が起きえて、EnforcedStyle: aggressive偽陽性が起きうるという。常時はデフォルトの EnforcedStyle: conservative のままとしている。パッチ会あたりでも聞いてみたいところ。

github.com あとは悲しいかな、誤検知をもちうる Cop のため git grep 'pluck.*uniq' なんかで置き換えが可能かどうかを見て判定というのが最初の導入として確実。まずは grep で対応した後に EnforcedStyle: conservative あたりで検出を行うというのが、まさに consavative なやり方になるだろう。型があれば誤検知なく EnforcedStyle: conservative が使えそうで未来の処理系に期待される。

そして、Rails/UniqBeforePluck cop は歴史的経緯ですでに名前が実体にあっていないので、RuboCop Rails 3.0 あたりでリネームされる予定です。

追記

同僚の kunitoo より例えば MySQL の collation によって、RDBMS によっては非互換が生まれる可能性があるかもという指摘があった。たしかに現状の実装だとオートコレクトの unsafe を検討できるかもしれない。