こんにちは、hachi8833です。
Active Support探訪シリーズ、今回は週刊Railsウォッチに何度か登場したActiveSupport::Duration
にお邪魔します。Core Extensionsの外としては初めてですね。何回かに分けてお送りします。
今回のメソッド
- メソッド: ActiveSupport::Duration以下の演算、比較、期間関連のメソッド
- ディレクトリ配置: https://github.com/rails/rails/blob/5-0-stable/activesupport/lib/active_support/duration.rb
- 歴史: Rails 2.0より導入
以下もご覧ください。
条件
- Railsバージョン: 5-0-stable
- Rubyバージョン: 2.4.0
ActiveSupport::Duration
のpublicメソッド
active_support/duration.rbにあるActiveSupport::Duration
のメソッドです。各コードについては次回のぞいてみます。
ActiveSupport::Duration
は日付や時刻による期間(時点と時点の間の大きさ)を表現するライブラリです。
Provides accurate date and time measurements using Date#advance and Time#advance, respectively. It mainly supports the methods on Numeric.
http://devdocs.io/rails~5.0/activesupport/durationで確認できるメソッドは以下です。
演算系
メソッド | 説明 |
---|---|
#+ |
Durationに別のDurationまたはNumericを足す Numericの単位は秒 |
#- |
Durationから別のDurationまたはNumericを引く Numericの単位は秒 |
#-@ |
Durationの符号を反転する単項演算子 |
以下はRailsコンソールでの実行結果です(Rubyのirbやpryで試す場合は明示的に`require 'active_support/all'を実行しておく必要があります)。
# サンプル
1.month + 3.days #=> 1 month and 3 days
1.month - 1.years #=> -1 year and 1 month
(1.month + 3.days).class #=> ActiveSupport::Duration
生成されているのはStringではなく、ActiveSupport::Duration
クラスのオブジェクトです。
比較系
メソッド | 説明 |
---|---|
#== |
引数(Durationの場合はその値)が同じ値を持つ場合true を返す |
#eql? |
引数がDurationクラスかつ値が同じ場合にtrueを返す |
# サンプル
dr1 = 1.month
dr2 = 30 * 24 * 60 * 60 # 1か月を秒で表す
dr1 == dr2 #=> true (値が同じ)
dr1.eql? dr2 #=> false(値は同じだがDurationではない)
dr2 = 10.months - 9.month
dr1.eql? dr2 #=> true (Durationかつ値も同じ)
dr2 = 31.days
dr1 == dr2 #=> false(1か月と31日は等しくない)
dr1.eql? dr2 #=> false(1か月と31日は等しくない)
期間系
メソッド | 説明 |
---|---|
#ago と#until |
引数をDurationで表される分さかのぼった新しいTimeかDateを返す デフォルトは現在時刻 |
#since と#from_now |
引数をDurationで表される分未来に進んだ新しいTimeかDateを返す デフォルトは現在時刻 |
Duration#until
はDuration#ago
のエイリアス、Duration#from_now
はDuration#since
のエイリアスです。
なお、これらのメソッドはDurationとDateTimeで実装状況が異なっていますので注意が必要です。
DateTime#ago
とDateTime#since
は実装されているDateTime#until
とDateTime#from_now
は実装されていない(エイリアスがない)
dr = 3.days #=> drはActiveSupport::Duration
dt = DateTime.now #=> dtはDateTimeオブジェクト
dr.method(:ago).owner #=> ActiveSupport::Duration
dt.method(:ago).owner #=> DateTime
dr.method(:until).owner #=> ActiveSupport::Duration
dt.method(:until).owner # NameError(未実装)
dr.method(:since).owner #=> ActiveSupport::Duration
dt.method(:since).owner #=> DateTime
dr.method(:from_now).owner #=> ActiveSupport::Duration
dt.method(:from_now).owner # NameError(未実装)
このため、DateTimeオブジェクトをレシーバーにすると#until
と#from_now
は動きません。
dr.ago dt #=> Sun, 29 Jan 2017 14:19:40 +0900
dt.ago 3.days #=> Sun, 29 Jan 2017 14:19:40 +0900
dr.until dt #=> Sun, 29 Jan 2017 14:19:40 +0900
dt.until 3.days # NoMethodError:
kazzさんが掘り当ててくれたActiveSupport::DateTimeの該当箇所のコードには#until
や#from_now
といったエイリアスは特に設定されていません。その割には#in
というエイリアスはあります。
#calculations.rb
class DateTime
def ago(seconds)
since(-seconds)
end
def since(seconds)
self + seconds
rescue
to_datetime.since(seconds)
end
alias :in :since
...
理由は不明ですが、until
という名前が制御文っぽいから嫌がられたのか、英語っぽくするためのシンタックスシュガーの必要を感じなかったのか、単にこれからエイリアスを設定するつもりなのか、あるいはDurationでは概念上#until
や#from_now
が必要だったのだろうか、などと考えてしまいました。機会があればもう少し追ってみたいと思います。
ActiveSupport::Duration
の残りのメソッドについては次回扱います。ご期待ください。