投稿日:2026/7/28
更新日:2026/7/28

where(status: statuses[:preparing]..) のような書き方は、3つの記法が合体している。
class Order < ApplicationRecord
enum :status, { draft: 0, cart: 10, preparing: 50, shipped: 60, delivered: 70 }
end
Order.where(status: Order.statuses[:preparing]..)
# => status >= 50 の注文(= preparing 以降のステータス全部)
statuses — enum が生成するマッピング Hashenum を定義すると、複数形のクラスメソッドが自動で生えて、定義したマッピングをそのまま返す。
Order.statuses
# => {"draft"=>0, "cart"=>10, "preparing"=>50, "shipped"=>60, "delivered"=>70}
Order.statuses[:preparing] # => 50
Order.statuses["preparing"] # => 50
ActiveSupport::HashWithIndifferentAccess なので、キーはシンボルでも文字列でも引ける。
役割は「50 という生の数字をコードに書かずに、名前から引く」こと。DB に入っているのは整数なので、比較には整数が必要になる。statuses[:preparing] を挟むことで、マッピングを変えたときにクエリ側を直さなくて済む。
50.. — 終端を省略した RangeRuby 2.6+ の endless range。50..Float::INFINITY のようなもの。
(50..).include?(60) # => true
(50..).end # => nil
先頭を省略した ..50(beginless range, Ruby 2.7+)もある。
where に Range を渡すと比較演算になるActiveRecord は Range を SQL の比較に変換する。終端がなければ >= だけになる。
where(status: 50..) # status >= 50
where(status: ..50) # status <= 50
where(status: 10..50) # status BETWEEN 10 AND 50
where(status: 10...50) # status >= 10 AND status < 50
.. と ...(終端を含むかどうか)の違いはそのまま SQL に出るので、境界値を扱うときは注意。
Order.where(status: :preparing) # ⭕ enum が 50 に変換してくれる
Order.where(status: :preparing..) # ❌ ArgumentError(bad value for range)
単体の値なら enum のキャストが効くが、Range の中までは面倒を見てくれない。だから statuses[:preparing].. と書いて整数に落とす必要がある。
範囲で絞るということは、enum の数値順が業務上の進行順と一致していることを暗黙の前提にしている。
enum :status, { draft: 0, cart: 10, preparing: 50, shipped: 60, delivered: 70 }
# ~~~~~~~~~~~~ ここ以降が「進行済み」
10 刻みにしてあるのは、後からステータスを挟めるようにするため。preparing の前に新ステータスを足したいときは 40、間に足したいときは 55 のように空き番号を使う。ここで既存の番号を振り直すと、status >= 50 という条件の意味が変わってしまう(そして DB の既存レコードも全件更新が必要になる)。
where(status: 50..) # ❌ 50 が何か読めない
where(status: statuses[:preparing]..) # ⭕ 「preparing 以降」と読める
生成される SQL は同じだが、後者は「なぜこの境界なのか」が名前で残る。