期間を指定するActiveRecordのクエリ

開始日と終了日がDateで与えられて「 開始日以降終了日まで」みたいなクエリをActiveRecordで書く時、

Event.where("date BETWEEN ? AND ?", start_date, end_date)

とかやるのだけれど、JOINした時などテーブル名が曖昧になって通らなくなったりするので、 ハッシュで書いてテーブル名の修飾をRailsに任せたい。

こういう場合はRangeが使えて、

Event.where(date: start_date...end_date)

とかできる。で、start_dateend_datenilの時には不等号で評価してほしいんだけど、 上2つの書き方だとどちらも動かない。

range_handler.rbを見る感じだと、Rangeの端の値がinfinity?に対してtrueを返せばよさそう。

ruby - Is there a way to express 'infinite time'? - Stack Overflowによると、DateTime::Infinity.newで無限大の未来を表せるらしい。このクラスのドキュメントさがせなかったけど。 問題は無限大の開始日が指定されなかった時に代わりに使う無限大の過去をどう表すかで、DateTime::Infinity.new(-1)はRangeに使うとエラーになってしまう。

[1] pry(main)> t = DateTime::Infinity.new(-1)
=> #<Date::Infinity:0x00007f852411a9d0 ...>
[2] pry(main)> t...Date.today
ArgumentError: bad value for range

とりあえずinfinity?に対応できればよさそうなので、特異メソッドで対応してみた。

MIN_DATE = Date.new(2000, 1, 1) #ダミーなので適当に
class << MIN_DATE
  def infinite?
    true
  end
end

start_date ||= MIN_DATE
end_date ||= DateTime::INFINITY.new

Event.where(date: start_date...end_date)

infinity? でなく infinite? なのは、begin_bindを作るときに query_attribute.rb でラップされるから(?)。

github.com このコミット見る感じ、PostgreSQL以外では通用しないのかな?

Ruby 2.5, Rails 5.2, PostgreSQL 10.5 で 開始日、終了日どちらか or どちらも指定した場合ともに動きました。