TimeWithZone & Time.zone.now integration test fails - ruby-on-rails

In a controller method I set a user's variable activation_sent_at equal to Time.zone.now when an activation email is sent to that user. On the development server this seems to work (although time expressions in my application are 2 hours behind on the local time of my computer).
I want to include an integration test that tests whether activation_sent_at indeed gets set properly. So I included the line:
assert_equal #user.activation_sent_at, Time.zone.now
However, this produces the error:
No visible difference in the ActiveSupport::TimeWithZone#inspect output.
You should look at the implementation of #== on ActiveSupport::TimeWithZone or its members.
I think it's suggesting to use another expression for Time.zone.now in my test. I've looked at different sources, including http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html, but am not sure what to do here. Any suggestions what could be causing this error?
Additional info: Adding puts Time.zone.now and puts #stakeholder.activation_sent_at confirms the two are equal. Not sure what generates the failure/error.

The issue is that the 2 dates are very close to each other but not the same. You can use assert_in_delta
assert_in_delta #user.activation_sent_at, Time.zone.now, 1.second
For RSpec, a similar approach would be to use be_within:
expect(#user.activation_sent_at).to be_within(1.second).of Time.zone.now

The problem is that your times are very close but not quite equal. They are probably off by a few fractions of a second.
One solution to issues like this is a testing gem called timecop. It gives you the ability to mock Time.now so that it will temporarily return a specific value that you can use for comparisons.

The reason is because Time.now or Time.zone.now include milliseconds (when you do a simple put to print the time it doesn't show milliseconds). However, when you persist the timestamp in the database these milliseconds likely get lost unless the db field is configured to store milliseconds. So when you read the value from the db it will not include milliseconds, hence the times are slightly different.
One solution is to remove milliseconds from Time.now. You can do this like so Time.now.change(usec: 0). This should fix the error in the tests.

Related

Convert Time.now into ActiveSupport::TimeZone

My website has a functionality of using User system time unless they are logged in.
The problem comes when trying to test this functionality, as Time.now.zone gives me a string representation of the zone instead of an ActiveSupport::TimeZone object.
The one I get from Time.now.zone also can't be used to look up the timezone:
Time.now.zone
=> 'BST'
while I need:
=> #<ActiveSupport::TimeZone:0x00007ffe1afd9470 #name="Europe/London", #utc_offset=nil, #tzinfo=#<TZInfo::DataTimezone: Europe/London>>
Use Time.zone instead of Time.now.zone
Thanks to #Stefan, my misunderstanding of Time.now has been cleared, it reporting server machine time, not user's, which of course makes all the sense. Thankfully, when it comes to tests, I can assume the client and server are the on the same machine, so its not a big problem.
I managed to also find a solution to my original problem. The method I was looking for was getlocal, which converts the DateTime object into system time(as mentioned above, that would be the server time, which is ok in tests). That made tests pass on both CircleCI and locally.

How to stub out Date that is implicitly referenced by years.ago in RSpec?

I know that I can stub out a method on Date like so:
allow(Date).to receive(:today).and_return Date.new(2015,11,10)
So now if within my spec the code calls Date.today I can be assured that it will return a Date object with the value of 11/10/2015.
I have a scope that utilizes years.ago.to_date. The user specifies the number of years ago for the scope: Ex: 5.years.ago, 2.years.ago.
I am trying to test this scope. In order to do so I need to control the Date that years.ago is referencing. For instance, I would always want the Date to be 1/1/2010. This way I will know that 5.years.ago will return 1/1/2005, and 2.years.ago would return 1/1/2008.
The issue is that I do not know what to stub out. I do not know how to keep the Date consistent which years.ago uses.
Hopefully this makes sense. I just need to control the Date that years.ago uses. How can I stub that out?
I looked a bit at ActiveSupport::Duration, but I'm not sure if that is the right place to look.
You should check timecop
Then in your tests, you could freeze the date to your desired value as follows:
describe "some set of tests to mock" do
before do
Timecop.freeze(2010, 1, 1)
end
after do
Timecop.return
end
it "should do blah blah blah" do
end
end
You can usually control what Ruby and Rails use for the current time by stubbing Time.now:
allow(Time).to receive(:now).and_return(Time.local 2016, 9, 6, 16, 51)
That does work for years.ago.
If your code, or the framework code you use, uses both Time.now and Date.today, however, timecop is easier.
If you use timecop, be aware that it's easy to forget to Timecop.return, which can screw up subsequent tests. Prefer timecop's safe mode.

Can I use PostgreSQL's "Special Date/Time Inputs" with ActiveRecord?

PostgreSQL supports several "Special Date/Time Inputs", strings that it interprets upon execution. Eg, 'now' means "current transaction's start time", and 'infinity' means "later than all other timestamps".
ActiveRecord does not seem to understand these - eg, SomeRecord.update!(updated_at: DateTime.current) works, but SomeRecord.update!(updated_at: 'now') tries to execute the UPDATE query with a NULL.
These special strings do work in Rails fixtures, because they go straight to the database. But is there a way to use them with an instantiated model?
You can set the value with raw_write_attribute(:updated_at, 'now')
Use .update_column
Eg, some_record.update_column(:updated_at, 'now').
Warning - this bypasses validations, callbacks, and normal setting of timestamps; it just issues an immediate UPDATE query.
Despite that, ActiveRecord contributor Sean Griffin said "update_column is probably the best 'hacky' answer" and that special string values like this aren't really well-supported and maybe shouldn't be. (I think I agree - it would make validation really awkward, both for ActiveRecord and in your own models.)
A compromise to get validations, etc
Given that this is a "direct to database" method, one possible compromise to avoid skipping validations and callbacks would be:
ActiveRecord::Base.transaction do
record.updated_at = Time.current # temporary value
record.save! # with validations, etc
record.update_column(:updated_at, 'now') # final value
end
Caveats for the console
A couple of other caveats I noticed for doing this in the console:
Inspecting the record afterwards, I saw 'now' as the value for created_at. Only after doing some_record.reload did I see the timestamp.
Doing this repeatedly didn't seem to give a new timestamp. I think that's because 'now' is "current transaction's start time", and maybe each rails console session uses one transaction. If I started a new console, I got a new value for 'now'

Temporarily setting system time with Ruby for unit testing

I've created a custom validation on a Apt model that verifies that an appointment can't be booked for the next day if the form submission is done after 3 PM and I'm trying to determine the best way to run the unit tests to verify that my custom validation is working correctly.
Currently, my test contains an if statement to to check if the test is run before or after 3 PM and uses a different test based on the current system time. However, I'd like to be able to test both cases no matter what the system time is.
test "booking appointment for tomorrow before/after three pm" do
a = appointments(:aptone)
# Set appointment date to tomorrow
a.apt_date = DateTime.current + 1.day
if (Time.current.hour >= 12)
assert(a.invalid?, 'Appointment CANNOT be booked after 3 PM')
elsif
assert(a.valid?, 'Appointment CAN be booked before 3 PM')
end
end
I'm wondering if there is a way to temporarily set the system time while running the test so that I can test both assertions regardless of when the tests are ran.
I can strongly recommend the timecop gem which was written for exactly the requirements you mentioned. Have a look at the documentation on the github page, specifically the freeze method (or travel, but it is better to test against a frozen system time in your example).
An easier approach would be to use a mocking framework to stub the response from the DateTime.current call to return your hardcoded value.
That avoids messing with your system clock.
System Time is related to your OS and not to your application. So if you wanted to set it then you can set it using
date --set="STRING"
in Linux.

How do I work with Time in Rails?

I've been pulling my hair out trying to work with Time in Rails. Basically I need to set all time output (core as well as ActiveSupport) to the server's local time -- no GMT, no UTC, etc. I've seen various posts relating to Time, but they usually involve someone's need to set it for each user. Mine isn't nearly as complex, I simply want consistency when I use any Time object. (I'd also appreciate not receiving errors every 3 seconds telling me that I can't convert a Fixnum (or some other type) to string -- it's Ruby, just do it!)
I also seem to be getting drastically different times for Time.new vs the ActiveSupport 1.second.ago. Anyway, does anyone have any quality suggestions as regards working with Time in Rails?
If you just want Time objects to be consistent, then why not stick with UTC? I just tried Time.new and 1.second.ago using script/console and I get the same output (give or take a second for typing the command). How are you doing it?
Somewhere in your initializers, define the format(s) that you want to use.
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(:default => '%m/%d/%Y %H:%M')
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(:my_special_format => '%H:%M %p')
Then when you want to print a Time object, it works like the following example. Notice that the Time object in my console is already aware of my time zone. I'm not performing any magical transformations here.
>> t = Time.now
=> Wed Jul 15 18:47:33 -0500 2009
>> t.to_s
=> "07/15/2009 18:47"
>> t.to_s(:my_special_format)
=> "18:47 PM"
Calling Time#to_s uses the :default format, or you can pass in the name of the format you'd rather use like I did with :my_special_format.
You can see the various options for formatting a Time object here.
If u don't want to store each user time setting, the only solution is to use javascript time system because it work on user client time. For example i have an application that each time user try it, the app will create some example data with each data have a initial date value "today". At first time, it confuse me a lot because my host server is in australia and lot of user is on western part, so sometime the initial date value is not "today", it said "yesterday" because of different time region.
After a couple day of headache i finally take decision to JUST use javascript time system and include it in the link, so when user click the "try now" link it will also include today date value.
<% javascript_tag do -%>
var today = new Date();
$("trynow").href = "<%= new_invitation_path %>?today=" + today.toLocaleString();
<% end -%>
Add the following to config/environment.rb to handle time correctly and consistently all the time within the context of Rails. It's important to know that it will store your times to the database in UTC -- but this is what you want -- all the conversion is done automatically.
config.time_zone = 'Pacific Time (US & Canada)'
You can run rake time:zones:local from your Rails root directory to get a list of valid time zone strings in your area.
A quick addition to the DATE_FORMAT solution posted above. Your format can be a string, in which case it works as noted above by calling strftime, but you can also define the format as a lambda:
CoreExtensions::Time::Conversions::DATE_FORMATS.merge! :my_complex_format => lambda {|time|
# your code goes here
}

Resources