Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

ActiveRecordで日付・時刻の範囲検索をシンプルに書く方法

更新情報

  • 2016/08/19: 初版公開
  • 2021/08/26: 更新

こんにちは、hachi8833です。

Active Recordで日付範囲を指定して読み出そうとすると、おそらく次のようなコードになるでしょう。

Pattern.where(“updated_at BETWEEN ? AND ?”, from, to)

社内のSlackチャンネルのログを遡ってて、Active Recordでwhere(updated_at: range_obj_start..range_obj_end)のように、Rangeオブジェクトを#whereの値指定として渡せるというやりとりを見つけたので、確認してみました。

範囲演算子とは

RubyのRangeオブジェクトでは、.....という範囲演算子を使えます。

条件式以外の場所では式1から式2までの範囲オブジェクトを返します。範囲オブジェクトはRangeクラスのインスタンスです。...で生成された範囲オブジェクトは 終端を含みません
範囲式 (Ruby 3.0.0 リファレンスマニュアル)より(強調は筆者)

終端を含まないのは.....のどっちだったかときどきわからなくなったりしますね。

なお、数学用語では端の値を含む範囲を「閉区間」、端の値を含まない範囲を「開区間」と呼んでいます(Wikipedia: 区間(数学))。

Range#newでオブジェクトを生成できます。ここでの範囲演算子は..なので閉区間ですね。

Range.new(Time.zone.now, Time.zone.now.tomorrow)

pry-rails gemを導入したRailsコンソールで出力しました。

range_new

.....で日時を範囲指定

範囲演算子を思い出したところで、適当なRailsプロジェクトをbundle exec rails cでコンソール起動し、Active Recordの適当なモデル(ここではPatternというモデル)のupdated_atカラムに次のクエリをそれぞれ実行してみます。両者の違いはRubyの範囲指定子.....だけです。

Pattern.where(updated_at: Time.zone.today.beginning_of_day..Time.zone.today.end_of_day).to_sql
Pattern.where(updated_at: Time.zone.today.beginning_of_day...Time.zone.today.end_of_day).to_sql

AR_range

1番目の..のSQLでは、ストレートにBETWEENを使っています。
2番目の...のSQLでは、end_of_dayに終端を含まないよう、<を使って自動展開しています。

SELECT `patterns`.* FROM `patterns` WHERE (`patterns`.`updated_at` BETWEEN '2016-08-18 00:00:00' AND '2016-08-18 23:59:59')
SELECT `patterns`.* FROM `patterns` WHERE (`patterns`.`updated_at` >= '2016-08-18 00:00:00' AND `patterns`.`updated_at` < '2016-08-18 23:59:59')

もし終端値のクラス(DateとかDatetimeとか)に応じて<<=を切り替えようとすると、クラスのチェックが必要になるので煩雑になってしまいます。

BETWEEN>=<の切り替えなら、終端値のクラスを気にせず、大小関係が定義されている値の範囲にシンプルに適用できます。ささやかですが、うまい処理ですね。

参考

関連記事

ActiveRecordのRangeHandlerクラスとRubyの範囲メソッドRange#exclude_end?


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。