我正在编写一个简单的类来将字符串解析为相对日期。
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
第一个似乎是一个时区问题,但另一个甚至相隔一天?我真的对此毫无头绪。
答案 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
以及year
和years
也是如此。]
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
方法。