rspec的时差

时间:2014-08-29 22:41:49

标签: ruby-on-rails ruby rspec

我正在编写一个简单的类来将字符串解析为相对日期。

module RelativeDate
  class InvalidString < StandardError; end
  class Parser
    REGEX = /([0-9]+)_(day|week|month|year)_(ago|from_now)/

    def self.to_time(value)
      if captures = REGEX.match(value)
        captures[1].to_i.send(captures[2]).send(captures[3])
      else
        raise InvalidString, "#{value} could not be parsed"
      end
    end

  end
end

代码似乎工作正常。

现在,当我尝试我的规格时,我只能在年和月得到时差

require 'spec_helper'
describe RelativeDate::Parser do
  describe "#to_time" do

    before do
      Timecop.freeze
    end

    ['day','week','month','year'].each do |type|
      it "should parse #{type} correctly" do
        RelativeDate::Parser.to_time("2_#{type}_ago").should == 2.send(type).ago
        RelativeDate::Parser.to_time("2_#{type}_from_now").should == 2.send(type).from_now
      end
    end

    after do
      Timecop.return
    end

  end
end

输出

..FF

Failures:

  1) RelativeDate::Parser#to_time should parse year correctly
     Failure/Error: RelativeDate::Parser.to_time("2_#{type}_ago").should == 2.send(type).ago
       expected: Wed, 29 Aug 2012 22:40:14 UTC +00:00
            got: Wed, 29 Aug 2012 10:40:14 UTC +00:00 (using ==)
       Diff:
       @@ -1,2 +1,2 @@
       -Wed, 29 Aug 2012 22:40:14 UTC +00:00
       +Wed, 29 Aug 2012 10:40:14 UTC +00:00

     # ./spec/lib/relative_date/parser_spec.rb:11:in `(root)'

  2) RelativeDate::Parser#to_time should parse month correctly
     Failure/Error: RelativeDate::Parser.to_time("2_#{type}_ago").should == 2.send(type).ago
       expected: Sun, 29 Jun 2014 22:40:14 UTC +00:00
            got: Mon, 30 Jun 2014 22:40:14 UTC +00:00 (using ==)
       Diff:
       @@ -1,2 +1,2 @@
       -Sun, 29 Jun 2014 22:40:14 UTC +00:00
       +Mon, 30 Jun 2014 22:40:14 UTC +00:00

     # ./spec/lib/relative_date/parser_spec.rb:11:in `(root)'

Finished in 0.146 seconds
4 examples, 2 failures

Failed examples:

rspec ./spec/lib/relative_date/parser_spec.rb:10 # RelativeDate::Parser#to_time should parse year correctly
rspec ./spec/lib/relative_date/parser_spec.rb:10 # RelativeDate::Parser#to_time should parse month correctly

第一个似乎是一个时区问题,但另一个甚至相隔一天?我真的对此毫无头绪。

1 个答案:

答案 0 :(得分:2)

这是一个引人入胜的问题!

首先,这与Timecop或RSpec无关。可以在Rails控制台中重现该问题,如下所示:

2.0.0-p247 :001 > 2.months.ago
 => Mon, 30 Jun 2014 20:46:19 UTC +00:00 
2.0.0-p247 :002 > 2.months.send(:ago)
DEPRECATION WARNING: Calling #ago or #until on a number (e.g. 5.ago) is deprecated and will be removed in the future, use 5.seconds.ago instead. (called from irb_binding at (irb):2)
 => Wed, 02 Jul 2014 20:46:27 UTC +00:00

[注意:此答案使用months的示例,但别名month以及yearyears也是如此。]

Rails将month方法添加到Integer类,返回ActiveSupport::Duration对象,这是一个&#34;代理对象&#34;包含method_missing方法,该方法重定向对&#34;值&#34;的method_missing方法的任何调用。它作为代理。

当您直接致电ago时,它会由ago类本身的Duration方法处理。但是,当您尝试通过ago调用send时,send中未定义Duration,并且BasicObject中未定义所有代理对象都继承自method_missing ,所以Rails的Duration方法&#39;调用send,然后在整数&#34;值&#34;上调用ago。代理,导致在Numeric中调用2*30。在您的情况下,这会导致日期更改等于Duration天。

您必须使用的唯一方法是BasicObject本身定义的方法和2.0.0-p247 :023 > BasicObject.instance_methods => [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__] 定义的方法。后者如下:

instance_eval

除了您发现的__send__之外,您还可以使用method_missing

以下是duration.rb

def method_missing(method, *args, &block) #:nodoc: value.send(method, *args, &block) end 的定义
value
在这种情况下,

Duration指的是method_missing对象中的秒数。如果您将ago重新定义为特殊情况send,则可以通过测试。或者您可以将__send__替换为class ActiveSupport::Duration alias send __send__ end ,如下所示:

method_missing

以下是来自Duration的{​​{1}}方法的另一个示例:

macbookair1:so1 palfvin$ rails c
Loading development environment (Rails 4.1.1)
irb: warn: can't alias context from irb_context.
2.0.0-p247 :001 > class ActiveSupport::Duration
2.0.0-p247 :002?>   def foo
2.0.0-p247 :003?>     'foobar'
2.0.0-p247 :004?>     end
2.0.0-p247 :005?>   end
 => nil 
2.0.0-p247 :006 > 2.months.foo
 => "foobar" 
2.0.0-p247 :007 > 2.months.respond_to?(:foo)
 => false 
2.0.0-p247 :008 > 

您可以直接调用新定义的foo,但因为BasicObject没有实现respond_to?,您无法测试&#34;该方法在那里定义。出于同样的原因,method(:ago)对象上的Duration会返回#<Method: Fixnum(Numeric)#ago>,因为这是ago上定义的value方法。