ActiveRecordのwhereで範囲検索する時、Rangeが想像以上に使える

Ruby on Rails

例えば where を使って特定の値を検索したい時、以下のように書けます。

User.where(age: age)

ただ「⚪︎以上」といった範囲検索がしたい時、

User.where('age >= ?', age)

みたいに私はこれまで書いてました。
「⚪︎以上⚪︎以下」といった範囲指定なら、上記のように文字列で指定することなく、

User.where(age: min..max)

Range クラスを使って指定していたので、同じように文字列使わないで指定できないかなぁと思っていました。
ただ最近、where('age >= ?', age) のように文字列指定しなくても済む方法を知ったので紹介します。

Range クラスで指定できる

「⚪︎以上⚪︎以下」の時と同様に、「⚪︎以上」の範囲検索でも Range を使えば実現できました。
始端・終端のいずれかに、Float::INFINITY を指定すればよかったのです。

min = 20
User.where('age >= ?', min)
# SELECT "users".* FROM "users" WHERE (age >= 20)
User.where(age: min..Float::INFINITY)
# SELECT "users".* FROM "users" WHERE "users"."age" >= 20

max = 65
User.where('age <= ?', max)
# SELECT "users".* FROM "users" WHERE (age <= 65)
User.where(age: -Float::INFINITY..max)
# SELECT "users".* FROM "users" WHERE "users"."age" <= 65

SQLもほとんど同じものが発行され、意味合いは同等になることが確認できます。

ただ、age < ? は Range の ... を活用すれば同様に指定できますが、

User.where(age: -Float::INFINITY...max)
# SELECT "users".* FROM "users" WHERE "users"."age" < 65

age > ? は Range の範囲オブジェクトとしては指定できません…

強いてあげるなら、以下のように not を活用すれば書けます。
が、素直に where('age > ?', age) のように書く方が可読性が高いと思いますので、あまりおすすめできません。

User.where.not(age: -Float::INFINITY..min) # age <= 20 を除外する -> age > 20
# SELECT "users".* FROM "users" WHERE "users"."age" > 20

WHERE NOT ("users"."age" <= 20) のようになると思ったけど、ちゃんと WHERE "users"."age" > 20 と読み替えて SQL 実行されてるのはすごい…!

Float::INFINITY を書く必要がなくなった

Ruby 側のバージョンアップにより、Ruby 2.6.0 からは、終端に nil を与えることで「終端を持たない範囲オブジェクト」が追加されました。
また、Ruby 2.7.0 では始端に nil を与えることで「始端を持たない範囲オブジェクト」も追加されています。
参考: https://docs.ruby-lang.org/ja/latest/class/Range.html

現時点ではRuby2系はサポート切れで3系に移行できているはずなので、以下の書き方もできます。

# いずれも同じ SQL が発行される
User.where(age: min..Float::INFINITY)
User.where(age: min..nil)
User.where(age: min..)

# いずれも同じ SQL が発行される
User.where(age: -Float::INFINITY..max)
User.where(age: nil..max)
User.where(age: ..max)

意味的にも分かりやすく簡潔なので、Float::INFINITYは使わず、始端・終端なしの書き方がよいかなと思います。

タイトルとURLをコピーしました