這是一個非常好的問題。簡短的回答是,1.month
是ActiveSupport::Duration
對象(因爲你已經看到的),其身份是兩種不同的方式定義:
- 爲
30.days
(如果你需要/嘗試將其轉換成秒數) ,和
- 爲1個月(如果您嘗試將此持續時間添加到日期)。
你可以看到,它仍然知道它是相當於1個月通過檢查其parts
方法:
main > 1.month.parts
=> [[:months, 1]]
一旦你看到的證據,它仍然知道它正是1個月,它是那麼神祕如果像Time.utc(2012,2,1) + 1.month
這樣的計算能夠給出正確的結果,即使是幾個月沒有準確的29天,以及爲什麼它給出的結果與Time.utc(2012,2,1) + 30.days
給出的結果不同。
ActiveSupport::Duration
如何隱藏自己的真實身份?
對我來說真正的祕密就是它如何很好地隱藏它的真實身份。我們知道它是一個ActiveSupport::Duration
的對象,但它很難得到它承認它是!
當您在一個控制檯(我用撬)檢查它,它看起來酷似(並聲稱是)正常Fixnum對象對象:
main > one_month = 1.month
=> 2592000
main > one_month.class
=> Fixnum
甚至聲稱自己是等同於30.days
(或2592000.seconds
),我們已經證明是不正確的(至少不是在所有情況下):
main > one_month = 1.month
=> 2592000
main > thirty_days = 30.days
=> 2592000
main > one_month == thirty_days
=> true
main > one_month == 2592000
=> true
所以要找出一個對象是否是一個ActiveSupport::Duration
或沒有,你不能依靠class
方法。相反,你必須直截了當地問:「你還是你不是ActiveSupport :: Duration的實例嗎?」與這樣一個直接的問題面前,有問題的對象將別無選擇,只能說老實話:
main > one_month.is_a? ActiveSupport::Duration
=> true
僅僅Fixnum對象的對象,而另一方面,必須團結他們的頭,承認自己不是:
main > 2592000.is_a? ActiveSupport::Duration
=> false
您也可以通過檢查告訴它除了定期對Fixnums如果響應:parts
:
main > one_month.parts
=> [[:months, 1]]
main > 2592000.parts
NoMethodError: undefined method `parts' for 2592000:Fixnum
from (pry):60:in `__pry__'
有一個數組部分是偉大的
關於有部分的陣列很酷的事情是,它可以讓你有被定義爲單位混合時間,像這樣:
main > (one_month + 5.days).parts
=> [[:months, 1], [:days, 5]]
這允許精確計算這樣事情:
main > Time.utc(2012,2,1) + (one_month + 5.days)
=> 2012-03-06 00:00:00 UTC
...它會不能夠正確計算,如果簡單地存儲只一些天或秒作爲其值。你可以看到自己這一點,如果我們先轉換1.month
到秒或幾天的「等效」號:
如何ActiveSupport::Duration
工作? (蓋瑞實現細節)
ActiveSupport::Duration
(在gems/activesupport-3.2.13/lib/active_support/duration.rb
)實際上定義爲BasicObject
一個子類,其中根據docs,「可被用於創建對象層次獨立Ruby的對象分層結構中,代理對象像委託人類,或者其他必須避免來自Ruby的方法和類的命名空間污染的使用。「
ActiveSupport::Duration
使用method_missing
將方法委託給其變量@value
變量。
獎金的問題:沒有人知道爲什麼一個ActiveSupport::Duration
對象索賠給不給迴應:parts
即使它實際上確實,爲什麼部分方法未在方法列表中列出?
main > 1.month.respond_to? :parts
=> false
main > 1.month.methods.include? :parts
=> false
main > 1.month.methods.include? :since
=> true
答:因爲BasicObject
沒有定義respond_to?
方法,發送respond_to?
到ActiveSupport::Duration
對象最終將調用其method_missing
方法,它看起來像這樣:
def method_missing(method, *args, &block) #:nodoc:
value.send(method, *args, &block)
end
1.month.value
僅僅是Fixnum 2592000
,所以它最終有效地呼叫2592000.respond_to? :parts
,這當然是false
。
這將是很容易解決的,但是,通過簡單地增加一個respond_to?
方法將ActiveSupport::Duration
類:
main > ActiveSupport::Duration.class_eval do
def respond_to?(name, include_private = false)
[:value, :parts].include?(name) or
value.respond_to?(name, include_private) or
super
end
end
=> nil
main > 1.month.respond_to? :parts
=> true
爲什麼methods
錯誤地忽略了:parts
方法的解釋是一樣的:因爲methods
消息只是獲得授權值,當然不是有一個parts
方法。我們可以像修改我們自己的methods
方法一樣容易地修復這個bug:
main > ActiveSupport::Duration.class_eval do
def methods(*args)
[:value, :parts] | super
end
end
=> nil
main > 1.month.methods.include? :parts
=> true
不錯!你知道'advance'被調用的地方/時間嗎?如果你能解釋爲什麼我沒有找到這個,而遵循我的問題,這將是有益的代碼。 – 2012-03-27 18:42:01
也,你在哪裏看到的代碼有選擇地使用基於Ruby版本? – 2012-03-27 18:44:46
查看Ruby版本代碼的鏈接文件(第10行)的頂部。 – Jon 2012-03-27 21:25:31