Sleep statement in Ruby doesn't accept any rails convertions - ruby-on-rails

I found this very weird. Why am I not able to do this?:
puts Time.now
x=2
puts x
puts x.class
sleep x
puts Time.now
x=x.seconds
puts x
puts x.class
sleep (x)
puts Time.now
The output I get is:
Mon Oct 01 16:14:58 +0530 2012
2
Fixnum
Mon Oct 01 16:15:00 +0530 2012
2
Fixnum
rake aborted!
can't convert ActiveSupport::Duration into time interval
/Users/hariharanganapathiraman/Documents/MigrationScripts/sample/lib/import/boot.rb:23:in `sleep'
Similarly it doesn't work for .minutes or .days.

That's because ActiveSupport::Duration mimics itself as Fixnum class:
ActiveSupport::Duration.new(3600, [[:seconds, 3600]]).class #=> Fixnum
Use sleep(2.seconds.to_i).

The problem is with ActiveSupport::Duration
It's inherited from BasicObject that have only limited number of methods and does not have class method.
If you take a look in ActiveSupport::Duration implementation you'll see that all methods are proxied(via method_missing) to #value, that's Fixnum in your case.
Hence, since ActiveSupport::Duration has no class method, your class call for a x.seconds
goes to Fixnum.

Related

Invalid Time.now on RSpec

How do I set the correct Time on RSpec.
[2] pry(#<RSpec::ExampleGroups::Device::Creation::Security>)> Time.now
=> Fri, 01 Jan 2016 10:00:00 CET +01:00
On rails console I have the correct time:
[1] pry(main)> Time.now
=> 2015-12-11 12:41:36 -0500
I don't want to stub it, because I'm not passing the time to my class. Have the following method in the class I'm testing:
def within_expiration?
Time.now - user.sms_sent_date < time_limit_in_seconds
end
This is not behaving as expected because Time.now is Jan 1, 2016
user.sms_sent_date has right date.
Make sure you don't have any library that is currently altering your Time in the spec.
For example, one of the most commonly used is Timecop. Make sure you don't setup it globally, instead use the blocks or before/after to properly mock the date and teardown the mock when no longer required.

How does method chaining work in Ruby?

How does method chaining work in ruby? As a C# developer, when I see 1.hour.from_now.localtime, I'm not sure what's going on. How does this code work?
<%= 1.hour.from_now.localtime %>
1 is an object that responds to hour
pry(main)> 1.class
=> Fixnum
.hour is a method on Fixnum that signifies it as an hour (by changing it to 3600)
pry(main)> 1.hour.class
=> Fixnum
pry(main)> 1.hour.to_i
=> 3600
.from_now changes the type of 3600 to a DateTime, 3600 seconds in to the future.
pry(main)> 1.hour.from_now
=> Mon, 22 Sep 2014 19:57:05 UTC +00:00
pry(main)> 1.hour.from_now.class
=> ActiveSupport::TimeWithZone
.localtime changes the TimeZone to the system local time:
pry(main)> 1.hour.from_now.localtime
=> 2014-09-22 12:57:41 -0700
Everything in Ruby is an object, so in this case:
The Fixnum 1 object receives the message hour
Which returns a Fixnum of seconds
3600 seconds receives the message from_now
Which returns an ActiveSupport::TimeWithZone object
Essentially it offsets the current time (the from_now) by an offset (our initial Fixnum of seconds)
This Time object then receives the message localtime
This converts the TimeWithZone object from UTC to your local timezone
http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html#method-i-localtime
If you want to explore each step, you can use the rails console and evaluate each step to see the return value (and call class on each step to see how it changes the type)

Time difference in rspec

I'm writing a simple class to parse strings into relative dates.
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
The code seems to work fine.
Now when I try my specs I get a time difference only in year and month
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
Output
..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
The first one seems like a time zone issue but the other one is even a day apart? I'm really clueless on this one.
This is a fascinating problem!
First, this has nothing to do with Timecop or RSpec. The problem can be reproduced in the Rails console, as follows:
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
[Note: This answer uses the example of months, but the same is true for the alias month as well as year and years.]
Rails adds the month method to the Integer class, returning an ActiveSupport::Duration object, which is a "proxy object" containing a method_missing method which redirects any calls to the method_missing method of the "value" it is serving as a proxy for.
When you call ago directly, it's handled by the ago method in the Duration class itself. When you try to invoke ago via send, however, send is not defined in Duration and is not defined in the BasicObject that all proxy objects inherit from, so the method_missing method of Rails' Duration is invoked which in turn calls send on the integer "value" of the proxy, resulting in the invocation of ago in Numeric. In your case, this results in a change of date equal to 2*30 days.
The only methods you have to work with are those defined by Duration itself and those defined by BasicObject. The latter are as follows:
2.0.0-p247 :023 > BasicObject.instance_methods
=> [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]
In addition to the instance_eval you discovered, you can use __send__.
Here's the definition of method_missing from duration.rb
def method_missing(method, *args, &block) #:nodoc:
value.send(method, *args, &block)
end
value in this case refers to the number of seconds in the Duration object. If you redefine method_missing to special case ago, you can get your test to pass. Or you can alias send to __send__ as follows:
class ActiveSupport::Duration
alias send __send__
end
Here's another example of how this method_missing method from Duration works:
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 >
You can call the newly defined foo directly, but because BasicObject doesn't implement respond_to?, you can't "test" that the method is defined there. For the same reason, method(:ago) on a Duration object returns #<Method: Fixnum(Numeric)#ago> because that's the ago method defined on value.

Ruby expression evaluation: whitespace matters?

Imagine it's Jan 19. This will not be hard if you look at this question today.
Date.today
=> Thu, 19 Jan 2012 # as expected
Date.today + 1
=> Fri, 20 Jan 2012 # as expected
Date.today+1
=> Fri, 20 Jan 2012 # as expected
Date.today +1
=> Thu, 19 Jan 2012 # ?!
What am I missing here?
The difference is that:
Date.today + 1
is an addition of two numerical values and
Date.today +1
is a call to the method today with the parameter sg(day of calendar reform) with value +1
The best way to examine this is to monkey patch the original method with debug output included. See this script as example:
require 'date'
class Date
def self.today(sg=ITALY)
puts "ITALY default("+sg.to_s+")" if sg==ITALY
puts sg unless sg==ITALY
jd = civil_to_jd(*(Time.now.to_a[3..5].reverse << sg))
new0(jd_to_ajd(jd, 0, 0), 0, sg)
end
end
puts "- Addition:"
Date.today + 1
puts "- Parameter:"
Date.today +1
This will print the following console output:
- Addition:
ITALY default(2299161)
- Parameter:
1
Yes, whitespace does matter in Ruby, contrary to popular belief. For example, foo bar is not the same as foobar.
In this particular case,
Date.today + 1
is the same as
Date.today().+(1)
Whereas
Date.today +1
is the same as
Date.today(+1)
which is the same as
Date.today(1.+#())

Override what Ruby thinks is the current time in Time.now?

I want to create test data for an application, and there are a lot of time_at attributes being tracked, too many to override in a maintainable way. What I'm thinking is, can I just change the base reference time variable in Ruby?
This would make it so created_at, updated_at, last_login_at, etc., could be set to an artificial time, so I could do this in tests:
Date.today #=> Thu, 30 Dec 2010
Time.system_time_offset = 1.week.ago # made up
Date.today #=> Thu, 23 Dec 2010
Time.now #=> Thu Dec 23 14:08:38 -0600 2010
user_1 = User.create!
user_1.created_at #=> Thu Dec 23 14:08:38 -0600 2010
Time.reset_system_time # made up
user_2 = User.create!
user_1.created_at #=> Thu Dec 30 14:08:38 -0600 2010
Is there a way to do this?
You could use Mocha to change the return value of Time.now during a test:
Time.stubs(:now).returns(Time.now - 1.day)
A good gem for this is Timecop: https://github.com/travisjeffery/timecop.
You can freeze time or change the time (while it continues to progress) very easily.
Ex.
Time.now
# => 2014-03-14 13:17:02 -0400
Timecop.travel 2.hours.ago
Time.now
# => 2014-03-14 11:17:04 -0400
Its nicer than the mocha solution since all time functions will be affected equally, so you won't have a test where Time.now is returning something different then DateTime.now
Its also more up-to-date than the time-warp gem suggested in another answer.
I use the timewarp gem for this sort of thing. You just put your code in a pretend_now_is(time) block and the code inside will be executed as if that was the actual time.
http://github.com/harvesthq/time-warp
Here's an example
def test_should_find_company_needing_reminded_today
pretend_now_is(Time.utc(2008,"jul",24,20)) do #=> Thu Jul 24 20:00:00 UTC 2008
#company.reminder_day = 'Thursday'
#company.save
companies = Company.find_companies_needing_reminded_today
assert_equal true, companies.include?(#company)
end
end
Honestly, I usually write tests for current time to check if the timestamp is within a reasonable range. i.e., check if the timestamp is greater than 1.minute.ago. Changing the system clock is likely to have all kinds of unpredictable side-effects, so you don't want to do that. You might be able to track down all the places in Ruby where the current time is accessed (though I think most methods just use Time.now) and monkey-patch them for the tests, but I'd probably still prefer just checking the timestamp is within a sane range.
It's also possible to (yuck) monkeypatch Time:
$start = Time.now - 86400 # this time yesterday
class Time
class << Time
def new
$start
end
def now
Time.new
end
end
end
puts(Time.now)
puts($start)

Resources