Everything works fine on local.
This doesn't work on Heroku:
class Ticket
def self.how_many_today
todays_tickets = Ticket.all.to_a.select!{ |t| t.created_at.to_date == Date.today }
todays_tickets == nil ? 0 : todays_tickets.count
end
# This method is scheduled with cron
def self.reset_todays_nr
#todays_nr = nil
end
def self.set_todays_nr
if #todays_nr.nil?
#todays_nr = how_many_today + 1
else
#todays_nr += 1
end
end
end
Namely, playing on heroku run console reveals this inconsistency:
irb(main):023:0* set_todays_nr
=> 1
irb(main):024:0> set_todays_nr
=> 2
irb(main):025:0> set_todays_nr
=> 3
irb(main):026:0> Ticket.all.to_a.select!{ |t| t.created_at.to_date == Date.today }
=> nil
irb(main):028:0> Ticket.first.created_at
=> Sat, 20 Dec 2014 16:19:31 UTC +00:00
irb(main):029:0> Ticket.first.created_at.to_date
=> Sat, 20 Dec 2014
irb(main):030:0> Date.today
=> Sat, 20 Dec 2014
irb(main):031:0> Date.today.to_date
=> Sat, 20 Dec 2014
irb(main):032:0> Date.today == Ticket.first.created_at.to_date
=> true
irb(main):033:0> Date.today.to_date == Ticket.first.created_at.to_date
=> true
irb(main):034:0>
irb(main):035:0* Ticket.all.to_a.select!{ |t| t.created_at.to_date == Date.today }
=> nil
irb(main):036:0> Ticket.all.map(&:created_at)
=> [Sat, 20 Dec 2014 16:19:31 UTC +00:00, Sat, 20 Dec 2014 16:21:12 UTC +00:00]
irb(main):037:0> _[0].to_date == Date.today
=> true
It looks like the condition for select! is properly parsed, manual check shows there are some elements to that condition, but select! does not return any array. Once again, this does work locally.
Database has been migrated and fixtures loaded just as on local.
Although self.reset_todays_nr is scheduled with cron which might cause problems, this method is not triggered in this case, so it's rather irrelevant for the problem, but I posted it here just in case this problem is more advanced than I suppose.
Could anyone help me out here, please?
That is weird indeed. Particularly because I ran some commands in my Rails console just now and array.select!{} shouldn't return nil unless the array was empty to begin with.
[1].select!{ |t| false } #=> []
[].select!{ |t| false } #=> nil
So recheck what the output of Ticket.all.to_a is.
Also, your select condition can be set simply as:
var = Ticket.select{ |t| t.created_at.to_date == Date.today }
That will select all tickets itself and then filter.
But it would be preferable to filter and count in the query rather than load up everything in memory and then do further operations for comparisons. Check this out:
Ticket.where("DATE(created_at) = DATE(?)", Date.today).count
Or change the DATE(?) part with your SQL's "get today's date" function.
Alternatively, you could:
now = DateTime.now
range = (today.beginning_of_day)..(today.end_of_day)
Ticket.where(created_at: range).count
Keep the possible discrepancy of time-zone in mind i.e. the created_at column might have a different time-zone than generated by DateTime.now. You'll have to check.
#Humza, thank you very much! It isn't the precise solution, but helped me to solve the case. Thanks a lot for Ticket.where("DATE(created_at) = DATE(?)", Date.today).count - I was thinking exactly the same, i.e. not to load the entire array and only then evaluate it, so thanks for a way of doing it. If you look at my code, you see that in set_todays_nr I purposedly place how_many_today in a condition so as to run the search at most as often as cron is scheduled to reset #todays_nr. After changing it, bug became more visible: because of the flow of the app, the new how_many_today was returning 1 - the ticket is created before this method is called. Though the mystery of strange heroku behaviour remains unsolved, I didn't sleuth further as changing the method to the below form solved the problem. Now it looks like this:
def self.how_many_today
Ticket.where("DATE(created_at) = DATE(?)", Date.today).count
end
# This method is scheduled with cron; check config/schedule.rb
def self.reset_todays_nr
#todays_nr = nil
end
def self.set_todays_nr
if #todays_nr.nil?
#todays_nr = how_many_today
else
#todays_nr += 1
end
end
Related
I have spec which checks calculation of difference between Time.now and created_at attribute of object. I stubbed Time.now, so this value is constant. Also I've set Time.now to created_at, but this value changes after reloading of object. How is it possible and how can I freeze created_at after object reloading?
This is an example of issue:
time = Time.now
=> 2015-03-19 15:50:13 UTC
Time.stubs :now => time
=> #<Expectation:0x9938830 allowed any number of times...
user = User.last
=> #<User:0x000000097a6e40...
user.update_attribute :created_at, Time.now - 1.minute
=> true
user.created_at
=> Thu, 19 Mar 2015 15:49:13 UTC +00:00
Time.now - user.created_at
=> 60.0
Time.now - user.reload.created_at
=> 60.442063277
I use rails 4.2.0, ruby 2.2.0 and rspec 2.14.1
Just reset nanoseconds:
time = Time.now.change(nsec: 0)
or milliseconds:
time = Time.now.change(usec: 0)
Here details.
In model/item.rb I have custom validation method
validate :double_dates
after_save :double_check
private
def double_dates
if Item.where(:user_id => self.user_id, :asin => self.asin, :domain => self.domain).where("DATE(created_at) = ?", Date.today).length >= 1
errors.add(:created_at, "no double dates")
end
Validation method does work in "rails c". But, when I run two task at the same time (rails runner "ApplicationController.rotate" I have Item.save in rotate method) - validation stops working.
irb(main):044:0> i1.created_at
=> Thu, 24 Apr 2014 02:41:15 UTC +00:00
irb(main):045:0> i2.created_at
=> Thu, 24 Apr 2014 02:41:15 UTC +00:00
irb(main):046:0> i1.created_at == i2.created_at
=> false
irb(main):047:0> i1.created_at.to_time.to_i == i2.created_at.to_time.to_i
=> true
irb(main):048:0> i1.created_at.class
=> ActiveSupport::TimeWithZone
irb(main):049:0> i2.created_at.class
=> ActiveSupport::TimeWithZone
irb(main):050:0> i1.created_at.usec => 311714
irb(main):051:0> i2.created_at.usec => 312779
Any hints or advice would be appreciated.
I had a similar problem once, and guess you could actually solve this problem adding indexes to the table. Add an index to the created_at column in Items.
Here you've got a full explanation about indexing in rails.
Hope it helps!
Thanks to #FabKremer I was able to solve it :)
by adding #i.created_at = Time.now.change(usec: 0) before Item.save :)
Nevertheless I'm accepting his answer, cuz it'll work for most cases.
irb(main):044:0> i1.created_at
=> Thu, 24 Apr 2014 02:41:15 UTC +00:00
irb(main):045:0> i2.created_at
=> Thu, 24 Apr 2014 02:41:15 UTC +00:00
irb(main):046:0> i1.created_at == i2.created_at
=> false
irb(main):047:0> i1.created_at.to_time.to_i == i2.created_at.to_time.to_i
=> true
Seems not to work validates_uniqueness_of :created_at
because
irb(main):046:0> i1.created_at == i2.created_at
=> false
How to validate created_at? Don't want to save with the same date.
+++ UPDATE +++
irb(main):048:0> i1.created_at.class
=> ActiveSupport::TimeWithZone
irb(main):049:0> i2.created_at.class
=> ActiveSupport::TimeWithZone
Since they might have different precision milliseconds.
Refer to the post: Testing ActiveSupport::TimeWithZone objects for equality
Chances are the millisecond values would be unequal.
puts i1.created_at.usec
puts i2.created_at.usec
I think, if you are getting concurrent requests, there are chances that you may have multiple entries in the table which are created at same time and will have same time stamps.
As you said, if you don't want to save with the same date, you can put a lock while saving the entries, removing the possibility of creating two entries at same time. In that case validates_uniqueness_of :created_at should also work.
Just in case
class Item < ActiveRecord::Base
attr_accessible :asin, :domain, :formatted_price, :user_id, :created_at
validate :double_dates
private
def double_dates
if Item.where(:user_id => self.user_id, :asin => self.asin, :domain => self.domain).where("DATE(created_at) = ?", Date.today).length >= 1
errors.add(:created_at, "no double dates")
end
end
end
I'm trying to write test that compare some dates. So far i have 2 tests, one of them works as intended, but the other one fails because doesnt/not correctly compare dates.
Here is my code:
def self.has_expired?(card, start_month, start_year, annually)
card_date = Date.new(card.year, card.month, -1)
billing_date = Date.new(start_year, start_month, -1)
if !annually
p '--------'
p card_date
p billing_date
card_date > billing_date
else
#return false
end
end
creditcard object
creditcard = ActiveMerchant::Billing::CreditCard.new(
:number => 1234567890123456
:month => 01,
:year => 13,
:first_name => 'John',
:last_name => 'Doe',
:verification_value => 132,
:brand => 'visa'
)
Here is output of p's
First block works as intended.
"--------"
Tue, 31 Jan 0013
Thu, 28 Feb 2013
false
Second block fails, expecting true, but got false
."--------"
Tue, 31 Jan 0013
Fri, 30 Nov 2012
false
Here is my rspec code
describe CreditCard do
context 'card_expired' do
it 'should return false with args passed to method (02month, 13 year, annually==false)' do
CreditCard.has_expired?(creditcard, 02, 2013, false).should == false
end
it 'should return true with args passed to method (11month, 12 year, annually==false)' do
CreditCard.has_expired?(creditcard, 11, 2012, false).should == true
end
end
end
in irb it works as charm, returning correct value(true/false)
I think the problem is in your logic. A card is expired when the expiration date is before the billing date, thus when
card_date < billing_date # expired
and not when
card_date > billing_date # valid
Also try puting in the full 2013 and see if that helps if it keeps breaking
:year => 2013,
You're also missing a comma after this line (probably a copy/paste error) :number => 1234567890123456
Is Time.zone.now.to_date equivalent to Date.today?
Another way to put it: will Time.zone.now.to_date == Date.today always be true?
If not, what's the best way to get a Date object corresponding to "now" in the application time zone?
They are not always the same. Time.zone.now.to_date will use the applications time zone, while Date.today will use the servers time zone. So if the two lie on different dates then they will be different. An example from my console:
ruby-1.9.2-p290 :036 > Time.zone = "Sydney"
=> "Sydney"
ruby-1.9.2-p290 :037 > Time.zone.now.to_date
=> Wed, 21 Sep 2011
ruby-1.9.2-p290 :038 > Date.today
=> Tue, 20 Sep 2011
Even easier: Time.zone.today
I also wrote a little helper method Date.today_in_zone that makes it really easy to get a "today" Date for a specific time zone without having to change Time.zone:
# Defaults to using Time.zone
> Date.today_in_zone
=> Fri, 26 Oct 2012
# Or specify a zone to use
> Date.today_in_zone('Sydney')
=> Sat, 27 Oct 2012
To use it, just throw this in a file like 'lib/date_extensions.rb' and require 'date_extensions'.
class Date
def self.today_in_zone(zone = ::Time.zone)
::Time.find_zone!(zone).today
end
end
I think the best way is to learn the current time through:
Time.current
This will automatically check to see if you have timezone set then it will call Time.zone.now, but if you've not it will call just Time.now.
Also, don't forget to set your timezone in application.rb
# system timezone
Time.now.to_date == Date.today
# application timezone
Time.zone.now.to_date == Time.current.to_date == Time.zone.today == Date.current
http://edgeapi.rubyonrails.org/classes/Time.html#method-c-current
http://edgeapi.rubyonrails.org/classes/Date.html#method-c-current