小ネタです。
条件に該当するレコードが存在するかを確認したい
ActiveRecordで条件に存在するレコードが存在するかを確認するのに、つい、
Model.where(conditions).present?
と書いてしまいがちですが、これはパフォーマンス上の問題が生じる可能性があります。
.present?
は ActiveSupport によってモンキーパッチされたメソッドです。
Mode.where(conditions)
のような ActiveRecord_Relation
クラスのオブジェクトに .present?
を適用するとどうなるか?
すると条件に該当するレコードを全てDBから取得して、(Rails上のモデルの)配列として評価することになります。
配列に要素が存在すれば true
、 しなければ false
ですね。
なぜこれはダメなのか
一見すると問題なさそうですし、実際結果自体は正しいのですが、条件に該当するレコードが1つでも存在することを確認できれば良いわけで、全件を取得する必要はありません。
100万レコードが条件に該当した場合、その100万レコードがDBからピックアップされ、その100万レコード分のデータがRails側に転送され、100万のモデルが作成された上で捨てられる。という壮大な無駄が発生します。結果、いつまでたっても結果が返ってこない。というパフォーマンス上の障害を引き起こしてしまうのです。
こういうのはレコード数の少ない開発環境では顕在化しずらく、レコード数の多い本番環境でいきなり顕在化してパニックになることがあります。
では、どうする?
.exists?
メソッドを使いましょう。
Model.where(conditions).exists?
こちらを使うと、SQLが exists
式を使うものに代わります。
exists
式は条件に該当するレコードが1件でも存在すればDBがそこで探索を打ち切ってくれるので、無駄に全件を取ってくることはありません。
DBレベルで true
または false
で返してくれるのでオーバヘッドが最小で済みます。
present? の逆は?
.present?
の逆である .blank?
に対応するメソッドは .empty?
になります。
.empty?
を使えばSQL上は exists
式を使います。
ただ、条件に該当するレコードが存在しないことを確認するには、結局全件のチェックが必要になるので両者( .blank?
と .empty?
)に速度的な差は少ないかもしれません。