こんにちは、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コンソールで出力しました。
..
と...
で日時を範囲指定
追記(2023/04/12)
以下のサンプルでは当日の範囲を取るのにTime.zone.today.beginning_of_day..Time.zone.today.end_of_day)
という長ったらしい書き方をしていましたが、当日の閉区間ならActive Supportのall_day
(Rails 5.1以降)を使う方がずっとシンプルに書けるというご指摘をいただきました。ありがとうございます!🙏
» Date.today.all_day
#>Wed, 12 Apr 2023 00:00:00.000000000 UTC +00:00..Wed, 12 Apr 2023 23:59:59.999999999 UTC +00:00
参考: Rails API: all_day
-- DateAndTime::Calculations
@techracho @hachi8833 "rails 日付 範囲"でググったら以下の記事がトップに出てきたんですが、".."なら範囲オブジェクトよりもall_dayメソッドを使った方がよりシンプルですね。可能なら追記をお願いします〜🙏https://t.co/p6DZOm5ujS
参考 https://t.co/KNtPdZiRmP— Junichi Ito (伊藤淳一) (@jnchito) April 12, 2023
元の書き方はRuboCop Railsにも怒られるRails/ExpandedDateRange
でした↓。
範囲演算子を思い出したところで、適当な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
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
と>=
〜<
の切り替えなら、終端値のクラスを気にせず、大小関係が定義されている値の範囲にシンプルに適用できます。ささやかですが、うまい処理ですね。
更新情報
追記: 以下の記事もどうぞ。