这是一个非常好的问题。简短的回答是1.month
is an ActiveSupport::Duration
对象(正如您已经看到的)及其标识以两种不同的方式定义:
- as
30.days
(如果您需要/尝试将其转换为秒数),and
- 为 1 个月(如果您尝试将此持续时间添加到日期中)。
你可以看到,通过检查它的值,它仍然知道它相当于 1 个月。parts
method:
main > 1.month.parts
=> [[:months, 1]]
一旦你看到它仍然知道现在正好是 1 个月的证据,计算结果就不那么神秘了Time.utc(2012,2,1) + 1.month
即使对于没有 29 天的月份也能给出正确的结果,以及为什么它给出的结果与Time.utc(2012,2,1) + 30.days
gives.
How do ActiveSupport::Duration
隐藏自己的真实身份?
对我来说,真正的谜团是它如何隐藏其真实身份如此之好。我们know它是一个ActiveSupport::Duration
对象,但很难得到它admit这是!
当你在控制台中检查它时(我正在使用 Pry),它看起来完全像(并且声称be) 一个普通的 Fixnum 对象:
main > one_month = 1.month
=> 2592000
main > one_month.class
=> Fixnum
它甚至声称相当于30.days
(or 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
...它会not只要简单存储就能正确计算only一些days or seconds作为它的价值。如果我们首先转换,您可以亲自看到这一点1.month
为其“等效”秒数或天数:
main > Time.utc(2012,2,1) + (one_month + 5.days).to_i
=> 2012-03-07 00:00:00 UTC
main > Time.utc(2012,2,1) + (30.days + 5.days)
=> 2012-03-07 00:00:00 UTC
如何ActiveSupport::Duration
工作? (血淋淋的实施细节)
ActiveSupport::Duration
实际上是定义的(在gems/activesupport-3.2.13/lib/active_support/duration.rb
)作为子类BasicObject
,根据docs http://www.ruby-doc.org/core-2.0/BasicObject.html,“可用于创建独立于 Ruby 对象层次结构的对象层次结构、代理对象(如 Delegator 类)或其他必须避免来自 Ruby 方法和类的命名空间污染的用途。”
ActiveSupport::Duration
uses method_missing
将方法委托给它@value
多变的。
奖金问题: 有谁知道为什么ActiveSupport::Duration
object claims不回应:parts
尽管实际上does,为什么 parts 方法没有列在方法列表中?
main > 1.month.respond_to? :parts
=> false
main > 1.month.methods.include? :parts
=> false
main > 1.month.methods.include? :since
=> true
Answer: 因为BasicObject
没有定义一个respond_to?
方法,发送respond_to?
to an ActiveSupport::Duration
对象最终会调用它的method_missing
方法,看起来像这样:
def method_missing(method, *args, &block) #:nodoc:
value.send(method, *args, &block)
end
1.month.value
只是 Fixnum2592000
,所以它实际上最终调用2592000.respond_to? :parts
,这当然是false
.
不过,这很容易解决,只需添加一个respond_to?
方法到ActiveSupport::Duration
class:
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
消息只是被委托给值,这当然是not have a parts
方法。我们可以像添加我们自己的错误一样轻松地修复这个错误methods
method:
main > ActiveSupport::Duration.class_eval do
def methods(*args)
[:value, :parts] | super
end
end
=> nil
main > 1.month.methods.include? :parts
=> true