Time.zone.now locked in production - ruby-on-rails

In prodction, method Time.zone.now always return the same value while Time.now returns
the correct system time.
Here one example:
banner.rb
scope :active, where("'#{Time.zone.now}' between start_date and end_date")
log of this scope:
SELECT `banners`.* FROM `banners` WHERE ('2013-03-06 08:06:46 -0300' between start_date and end_date) AND ((`banners`.`store_id` = 1 AND `banners`.`spot_id` = 3 AND `banners`.`at_home` = 1))
Time.zone.now is always 2013-03-06 08:06:46 -0300
Anyone know why?

This is because the scope is evaluated once, when the class is loaded.
You should define it like this:
def self.active
where("'#{Time.zone.now}' between start_date and end_date")
end
Every time this method is called, it will be re-evaluated and so the time will change.

Related

(Rails) Test fails during late night time

My rails application is covered by many tests. All the tests pass in regular situations. That is, excluding when it's late at night.
There's actually a few tests that end up failing when it's night. All these tests involve modifying a model's time attribute and seeing if related models are affected.
test "changing time should affect hours" do
// ..User is loaded.
user.attend(event)
assert_equal 1, user.hours // User attends a 1 hour event that has passed.
// Move event to the future.
event.update_attributes(date: Date.today,
start_time: Time.now,
end_time: Time.now + 1.hour)
assert_equal 0, attendance_of(user).hours // Passes in day, fails during night
end
test "valid event creation" do
// Count does NOT change by 1 at night.
assert_difference '#group.events.count', 1 do
post group_events_path(#group), event: { ...
date: Date.today,
start_time: Time.now,
end_time: Time.now + 1.hour,
... }
end
end
What is going on here? For reference, here's what I currently use to determine when to update an attendance (which is something that an event has). This comes from the event controller:
def not_ended?
date.future? || (date.today? &&
(Time.now.seconds_since_midnight < end_time.seconds_since_midnight))
end
def update_attendances
// ... Determine the new date, start, and end time values through ActiveRecord::Dirty
if not_ended?
remove_checks = true
end
attendances.each do |attendance|
new_checked = remove_checks ? false : attendance.checked
attendance.update_attributes(went: new_start, left: new_end,
checked: new_checked)
end
end
end
Validating an event to make sure its times aren't weird:
def valid_time
if start_time == end_time
// Error...
end
if start_time > end_time
// Error...
end
end
Time zone in application.rb:
config.time_zone = 'Pacific Time (US & Canada)'
Use Time.zone.now or timecop.freeze to freeze the timezone as your test is probably using system time and not rails app config time
Your not_ended? method is broken. It doesn't work when the event starts before midnight, but ends after. In that case the date is today (assuming the data is based on start time) but the number of seconds since midnight of the end of the event is less that the current time.
In these situations you shouldn't be trying to deal with dates and times separately. You should have a way to retrieve the datetime of the end of the event and compare that with the current datetime.

How to reset timestamp to "nil" at end of day?

At the end of each day I intended for this code to reset the timestamp completed_at back to nil, but it's not working.
def completed=(boolean)
self.completed_at = boolean ? Time.current : nil
end
def completed
completed_at && completed_at >= Time.current.beginning_of_day
end
What's the point?
The user checks off in the view that he did his habit:
<%= link_to ''.html_safe, mark_completed_path(habit), remote: true, method: 'put', class: 'update_habit' %>
The habit then fadeOut's but the idea is that the habit should return the following day so that everyday the user must check off the habit again.
def home
#habits = current_user.habits.committed_for_today.incomplete.order(:order)
end
You dont need to set the time to nil at the end of the day, you need to change your scope for incomplete so that it will consider all habits to be incomplete where completed_at is not its current date
if you want all habits, where completed_at is less than the current date. do this
scope: incompleted, -> {where("completed_at is null OR completed_at < ?", Date.current)}
This way you dont need any rake task and your logic will be used to consider habits incomplete per day.
Precedence issue, try:
def completed=(boolean)
self.completed_at = (boolean ? Time.current : nil)
end

ActiveRecord where method datetime passed

Thanks for your continuing support in my latest venture in a Rails app. It's been a while since I've made something in Rails so here is my latest question. I appreciate the help you've all given in the past.
I have a Model called Event.rb which contains a date:date and time:time fields for the Event.
I also have a method inside of the model which is..
def begins
DateTime.new(date.year, date.month, date.day, time.hour, time.min, time.sec)
end
As I can't see if something has truly passed because I only have Date and Time separate so I need them together.
My question is...
I want to be able to add in the DateTime :begins into the following other method in my Model...
def self.past
where("date <= ?", TIME_NOW)
end
Just like I have a method which is...
def upcoming?
self.date >= Time.now
end
Which I could easily change self.date to begins and would past I would imagine?
Thanks!
Perhaps something like this will work for querying the database for past events using your existing date and time columns:
scope :past, lambda {
where("date <= ? and time <= ?",
Time.now.strftime("%Y-%d-%m"),
Time.now.strftime("%H:%M:%S")
)
}
past_events = Event.past
For checking the current instance, you could continue to use your begins method:
def past?
begins < DateTime.now
end
#event = Event.first
#event.past?

Rails/Postgres query rows grouped by day with time zone

I'm trying to display a count of impressions per day for the last 30 days in the specific users time zone. The trouble is that depending on the time zone, the counts are not always the same, and I'm having trouble reflecting that in a query.
For example, take two impressions that happen at 11:00pm in CDT (-5) on day one, and one impression that happens at 1:00am CDT. If you query using UTC (+0) you'll get all 3 impressions occurring on day two, instead of two the first day and one the second. Both CDT times land on the day two in UTC.
This is what I'm doing now, I know I must be missing something simple here:
start = 30.days.ago
finish = Time.now
# if the users time zone offset is less than 0 we need to make sure
# that we make it all the way to the newest data
if Time.now.in_time_zone(current_user.timezone) < 0
start += 1.day
finish += 1.day
end
(start.to_date...finish.to_date).map do |date|
# get the start of the day in the user's timezone in utc so we can properly
# query the database
day = date.to_time.in_time_zone(current_user.timezone).beginning_of_day.utc
[ (day.to_i * 1000), Impression.total_on(day) ]
end
Impressions model:
class Impression < ActiveRecord::Base
def self.total_on(day)
count(conditions: [ "created_at >= ? AND created_at < ?", day, day + 24.hours ])
end
end
I've been looking at other posts and it seems like I can let the database handle a lot of the heavy lifting for me, but I wasn't successful with using anything like AT TIME ZONE or INTERVAL.
What I have no seems really dirty, I know I must missing something obvious. Any help is appreciated.
Ok, with a little help from this awesome article I think I've figured it out. My problem stemmed from not knowing the difference between the system Ruby time methods and the time zone aware Rails methods. Once I set the correct time zone for the user using an around_filter like this I was able to use the built in Rails methods to simplify the code quite a bit:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
around_filter :set_time_zone
def set_time_zone
if logged_in?
Time.use_zone(current_user.time_zone) { yield }
else
yield
end
end
end
# app/controllers/charts_controller.rb
start = 30.days.ago
finish = Time.current
(start.to_date...finish.to_date).map do |date|
# Rails method that uses Time.zone set in application_controller.rb
# It's then converted to the proper time in utc
time = date.beginning_of_day.utc
[ (time.to_i * 1000), Impression.total_on(time) ]
end
# app/models/impression.rb
class Impression < ActiveRecord::Base
def self.total_on(time)
# time.tomorrow returns the time 24 hours after the instance time. so it stays UTC
count(conditions: [ "created_at >= ? AND created_at < ?", time, time.tomorrow ])
end
end
There might be some more that I can do, but I'm feeling much better about this now.
Presuming the around_filter correctly works and sets the Time.zone in the block, you should be able to refactor your query into this:
class Impression < ActiveRecord::Base
def self.days_ago(n, zone = Time.zone)
Impression.where("created_at >= ?", n.days.ago.in_time_zone(zone))
end
end

Get objects size between relative dates

I have many User objects with created_at attribute e.g.
I get objects with #users = User.all
I want get the count of User objects with various ages from creation with
#users.size
for these date ranges:
yesterday
last week
last month
last year.
How can I do it?
I use mongoid.
You can write scopes for this:
class User
include Mongoid::Document
...
## Scopes for calculating relative users
scope :created_yesterday, lambda { where(:created_at.gte => (Time.now - 1.day)) }
scope :created_last_week, lambda { where(:created_at.gte => (Time.now - 1.week)) }
scope :created_last_month, lambda { where(:created_at.gte => (Time.now - 1.month)) }
scope :created_last_year, lambda { where(:created_at.gte => (Time.now - 1.year)) }
...
end
The reason we need to use a lambda here is that it delays the evaluation of the Time.now argument to when the scope is actually invoked. Without the lambda the time that would be used in the query logic would be the time that the class was first evaluated, not the scope itself.
Now we can get the counts, by simply calling:
User.created_yesterday.count
User.created_last_week.count
...
If you want the objects:
#users_created_yesterday = User.created_yesterday
You can use #users_created_yesterday in the views.
Update
Well with yesterday, if you mean time between, yesterday beginning of day 0:00 and yesterday end of day 23:59, you will have to take Time zones into consideration.
Fo example, in your application.rb, if you have written:
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
config.time_zone = 'Central Time (US & Canada)'
If you have use this, all the times fetched by activerecord queries will be converted to this time zone, Central Time (US & Canada). In the database, all the times will be stored in UTC and will be converted to the time zone on fetching from the database. If you have commented out this line, default will be UTC. You can get all the time zones using the rake task rake time:zones:all or only the US timezones using rake time:zones:us.
If you want the time zone specified in the application.rb, you need to use Time.zone.now, in the following code. If you use Time.now in the following code, you will get the time zone according to the time zone of your server machine.
class User
include Mongoid::Document
...
scope :created_between, lambda { |start_time, end_time| where(:created_at => (start_time...end_time)) }
class << self
## Class methods for calculating relative users
def created_yesterday
yesterday = Time.zone.now - 1.day
created_between(yesterday.beginning_of_day, yesterday.end_of_day)
end
def created_last_week
start_time = (Time.zone.now - 1.week).beginning_of_day
end_time = Time.zone.now
created_between(start_time, end_time)
end
def created_last_month
start_time = (Time.zone.now - 1.month).beginning_of_day
end_time = Time.zone.now
created_between(start_time, end_time)
end
def created_last_year
start_time = (Time.zone.now - 1.year).beginning_of_day
end_time = Time.zone.now
created_between(start_time, end_time)
end
end
..
..
end
So, you can write class methods and calculate start time and end time, supply it to the scope created_between, and you will be able to call them like User.created_yesterday, like we called before.
Credits: EdgeRails. Since it is Mongoid, I had to look up at Mongoid docs

Resources