Ruby on rails ActiveSupport如何进行月度合计?

Ruby on rails ActiveSupport如何进行月度合计?,ruby-on-rails,ruby,activesupport,Ruby On Rails,Ruby,Activesupport,我很高兴也很惊讶地发现,ActiveSupport以我希望的方式完成了每月的合计。无论所讨论的月份有多少天,将1.month添加到特定的时间将使您在该月的同一天与时间 > Time.utc(2012,2,1) => Wed Feb 01 00:00:00 UTC 2012 > Time.utc(2012,2,1) + 1.month => Thu Mar 01 00:00:00 UTC 2012 activesupport提供的Fixnum中的months方法没有提供线

我很高兴也很惊讶地发现,ActiveSupport以我希望的方式完成了每月的合计。无论所讨论的月份有多少天,将
1.month
添加到特定的
时间
将使您在该月的同一天与
时间

> Time.utc(2012,2,1)
=> Wed Feb 01 00:00:00 UTC 2012
> Time.utc(2012,2,1) + 1.month
=> Thu Mar 01 00:00:00 UTC 2012
activesupport提供的
Fixnum
中的
months
方法没有提供线索:

def months
  ActiveSupport::Duration.new(self * 30.days, [[:months, self]])
end
遵循
+
时间
中的
+
方法

def plus_with_duration(other) #:nodoc:
  if ActiveSupport::Duration === other
    other.since(self)
  else
    plus_without_duration(other)
  end
end
def since(time = ::Time.current)
  time + self
end
…将我们引向
,因为
Fixnum
中的

def plus_with_duration(other) #:nodoc:
  if ActiveSupport::Duration === other
    other.since(self)
  else
    plus_without_duration(other)
  end
end
def since(time = ::Time.current)
  time + self
end
…这让我们一事无成


ActiveSupport(或其他东西)如何/在哪里做聪明的月份计算而不是仅仅增加30天?

看起来ActiveSupport的神奇之处在于:


看起来Ruby 1.9+可以处理这个问题,所以这段代码只在Rails与较旧的Ruby版本一起使用时使用。

这是一个非常好的问题。简单的回答是,
1.month
是一个
ActiveSupport::Duration
对象(正如您已经看到的),它的标识以两种不同的方式定义:

  • 30.天
    (如果您需要/尝试将其转换为秒数),以及
  • 1个月(如果您试图将此持续时间添加到日期)
通过检查其
零件
方法,您可以看到它仍然知道它相当于1个月:

main > 1.month.parts
=> [[:months, 1]]
main > ActiveSupport::Duration.class_eval do
         def methods(*args)
           [:value, :parts] | super
         end
       end
=> nil

main >  1.month.methods.include? :parts
=> true
一旦你看到证据表明它仍然知道正好是1个月,那么像
Time.utc(2012,2,1)+1.month
这样的计算是如何给出正确的结果的就不那么神秘了,即使对于没有正好29天的月份,为什么它给出的结果不同于
Time.utc(2012,2,1)+30.days
给出的结果

如何
ActiveSupport::Duration
隐藏他们的真实身份?

对我来说,真正的谜团是它如何如此好地隐藏自己的真实身份。我们知道它是一个
ActiveSupport::Duration
对象,但很难让它承认它是

当您在控制台中检查它时(我使用的是Pry),它看起来完全像(并声称是)普通的Fixnum对象:

main > one_month = 1.month
=> 2592000

main > one_month.class
=> Fixnum
它甚至声称相当于
30.天
(或
2592000.秒
),我们已经证明这是不正确的(至少不是在所有情况下):

因此,要确定一个对象是否是
ActiveSupport::Duration
,您不能依赖
class
方法。相反,你必须直截了当地问:“你是否是ActiveSupport::Duration的实例?”面对这样一个直接的问题,所讨论的对象将别无选择,只能承认事实:

main > one_month.is_a? ActiveSupport::Duration
=> true
另一方面,纯粹的Fixnum对象必须低头承认它们不是:

main > 2592000.is_a? ActiveSupport::Duration
=> false
您还可以通过检查它是否响应
: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]]
def method_missing(method, *args, &block) #:nodoc:
  value.send(method, *args, &block)
end
这使其能够准确计算以下各项:

main > Time.utc(2012,2,1) + (one_month + 5.days)
=> 2012-03-06 00:00:00 UTC
。。。如果它只存储天数或秒数作为其值,则无法正确计算。如果我们首先将
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
实际上被定义为
BasicObject
的一个子类,根据,可用于创建独立于Ruby对象层次结构的对象层次结构、代理对象(如Delegator类)或其他必须避免Ruby方法和类的命名空间污染的用途

ActiveSupport::Duration
使用
method\u missing
将方法委托给其
@value
变量

奖金问题:有人知道为什么一个
ActiveSupport::Duration
对象声称不响应
:parts
,即使它确实响应,以及为什么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\u to?
方法,因此将
respond\u to?
发送到
ActiveSupport::Duration
对象将调用其
方法(缺少
方法),如下所示:

main > (one_month + 5.days).parts
=> [[:months, 1], [:days, 5]]
def method_missing(method, *args, &block) #:nodoc:
  value.send(method, *args, &block)
end
1.month.value
只是Fixnum
2592000
,因此它实际上最终调用了
2592000.response\u to?:parts
,这当然是
false

不过,只要在
ActiveSupport::Duration
类中添加一个
respond\u to?
方法,就很容易解决这个问题:

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
为什么
方法
错误地忽略了
:parts
方法的解释是相同的:因为
方法
消息只是被委托给值,而值当然没有
parts
方法。我们可以像添加自己的
方法一样轻松地修复此错误:

main > 1.month.parts
=> [[:months, 1]]
main > ActiveSupport::Duration.class_eval do
         def methods(*args)
           [:value, :parts] | super
         end
       end
=> nil

main >  1.month.methods.include? :parts
=> true

很好!你知道在哪里/什么时候调用
advance
吗?如果你能解释一下为什么我在跟踪问题中的代码时没有找到这个,那会很有帮助。另外,你在哪里看到基于ruby版本的代码被选择性地使用?查看链接文件的顶部(第10行)对于Ruby版本的code.good stuff——我仍然不知道advance在哪里用于求和,我只看到它在
月前
周前
等中使用。