Rails scope with combined attributes - ruby-on-rails

I am trying to create a scope where checkin_time (DateTime) is added with duration (Int) in hours which is greater than or equal to Time.now
So something like this
scope :current, -> { where('checkin_time + duration.hours >= ?', Time.now) }
However, the string 'checkin_time + duration.hours >= ?' above is not valid. How can I achieve something that will give me the correct result?

The easiest thing to do with PostgreSQL is to convert your duration to an interval and add that interval to your timestamp. Something like this:
where("checkin_time + (duration || 'hours')::interval >= ?", Time.now)
or even:
where("checkin_time + (duration || 'hours')::interval >= now()")
The duration || 'hours' builds a string like '6hours' and then the ::interval casts that string to an interval which can be added to a timestamp.

Related

Convert Timestamp from PostgreSQL to Time Zone in Query (Rails)

Past the hours of head-banging for this one.
I am attempting to separate the query model from PG for the model start_time (stored in UTC) by day group.
Morning (12a - 12p)
Afternoon (12p - 5p)
Evening (5p - 12a)
I've tried scope methods, queries by instance methods, and overall class methods. All of which return the day group by UTC (not the local time zone for the scheduled event)
# event.rb
def self.morning
startday = 0
midday = 12
Event.where("extract(hour from start_time) >= ? AND extract(hour from start_time) < ?", startday, midday)
end
Also tried,
def self.afternoon
midday = "12:00:00"
eveday = "17:00:00"
Event.where("start_time::time >= ? AND start_time::time < ?", midday, eveday)
end
When console prompting (and generally throughout the app) I call event.start_time is successfully returned in the local time zone (set in the application.rb file)
But unless called outside of the model, the start_time continues to query as UTC.
I do not want to preset the DB timezone (as this is bad practice and the app is used globally)
Edit
As an example in the view, I am calling
<% events.morning.order("start_time ASC").each do |fit_class| %>
...
where,
events = #events = Event.all # passed through a partial
Just add scopes for Event model with time zone checks
class Event < ApplicationRecord
scope :morning, -> { where(
"start_time >= ? AND start_time < ?",
formatted_time(0), formatted_time(12))
}
scope :afternoon, -> { where(
"start_time >= ? AND start_time < ?",
formatted_time(12), formatted_time(17))
}
scope :evening, -> { where(
"start_time >= ? AND start_time < ?",
formatted_time(17), formatted_time(24))
}
private
def formatted_time(hour = 0)
Time.zone.parse(Date.current.to_s).change(hour: hour)
end
end
You don't have to modify any other time zone settings for this query. Hope it helps!
For onlookers, my temporary solution is as follows (MUCH research into the PostgreSQL docs):
def self.morning
startday = 0
midday = 12
Event.where("extract(hour from start_time - interval '6 hours') >= ? AND extract(hour from start_time - interval '6 hours') < ?", startday, midday)
end
def self.afternoon
midday = 12
eveday = 17
Event.where("extract(hour from start_time - interval '6 hours') >= ? AND extract(hour from start_time - interval '6 hours') < ?", midday, eveday)
end
def self.evening
eveday = 17
endday = 24
Event.where("extract(hour from start_time - interval '6 hours') >= ? AND extract(hour from start_time - interval '6 hours') < ?", eveday, endday)
end
This required me to preset the application time zone.
# application.rb
config.time_zone = "Central Time (US & Canada)"
I will change post the full-solution update when discovered!

Ruby on Rails, Select Users with age range from Active Record

User.rb
# Attributes
# (..)
# birthdate (string)
# format "mm/yyyy"
def age
dob = self.birthdate.to_date
now = Time.now.utc.to_date
now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
end
In the console:
irb(main):002:0> current_user.age
=> 7
I'd be able to do the following:
age_range = "25-65"
User.where(:age => between age_range)
I'm stuck at the point how to get the value from age (class method) into the where call
First of all: use a date as type for birthdate in the database.
Then you can just use:
User.where(birthdate: 65.years.ago..25.years.ago)
If you can't change the birthdate type convert it using SQL (example with PostrgeSQL):
User.where('to_date(birthdate, 'MM/YYYY') between ? and ?', 65.years.ago, 25.years.ago)
But you may still have to correct it since you don't have the exact day and only the month.
With PostgreSQL you can use that Rails scope
scope :for_age_range, -> min, max {
where("date_part('year', age(birthdate)) >= ? AND date_part('year', age(birthdate)) <= ?", min, max)
}
User.for_age_range(18, 24)

Rails: How to loop through month?

I made a scope help me to select objects
scope :best_of_the_month, ->(year, month) do
time = Time.new(year, month)
start_time = time.beginning_of_month
end_time = time.end_of_month
where("created_at > ? AND created_at < ?", start_time, end_time).where("likes > ?", 15).where("rating > ?", 4.85).order_by_rating.to_a.uniq(&:author)
end
Then, I want to loop through this method, from 2014/1 to now. How can I do it?
Maybe something like this:
start_date = Date.create(2014,1).month
end_date = Date.today.month
#monthly_videos = []
(start_date..end_date).each do |year, month|
videos = Video.best_of_the_month(year, month)
#monthly_videos << videos
end
I find a solution here, How to Loop through Months in Ruby on Rails. But it seems about looping through the days. Not the month
With the best_of_the_month scope defined to take month and year as params, the following code should work:
date = Date.new(2014,1,1)
#monthly_videos = []
while true
videos = Video.best_of_the_month(date.year, date.month)
#monthly_videos << videos
date += 1.month
break if date == Date.today.beginning_of_month
end
You could use the Date#next_month method
date = Date.new(2014,1,1)
final_date = Date.new(Date.today.year, Date.today.month)
#monthly_video = []
loop do
#monthly_videos << Video.best_of_the_month(date.year, date.month)
break if date == final_date
date = date.next_month
end

Rails4 - Query by time hour and/or time minute?

I've got a ScheduleTime ActiveRecord object, and time is of column_type :time.
I'm attempting to retrieve objects that match the current hour and minute
ScheduleTime.all.select do |s|
now = Time.now
s.time.hour == now.hour && s.time.min == now.min
end
Can this be done with SQL in a where clause?
EDIT:
I've gotten closer, i can query by the hour:
ScheduleTime.where("date_part('hour', time) = ?", Time.now.hour)
EDIT#2:
This is my current implementation...
class ScheduleTime < ActiveRecord::Base
belongs_to :schedule
validates_presence_of :schedule_id, :time
scope :by_hour, ->(hour) { where("date_part('hour', time) = ?", hour) }
scope :by_minute, ->(minute) {where("date_part('minute', time) = ?", minute) }
scope :by_time, ->(time) { by_hour(time.hour).by_minute(time.min) }
scope :now, -> { by_time(Time.now) }
end
The final version looks fine. Just make sure to use Time.zone.now instead of Time.now as the former doesn't take your application's configured time zone into account.

Issue with Time and ranges

I'm currently working on an Appointment system and building it with Ruby on Rails. I have an Appointment model and appointments controller where on the index, I want to show a list of appointments for that day, separated by 30 minute chunks.
I have a basic working version and I've got a ruby method that adds a class on the table row which shows the if the current 30 minute chunk is the current time or not.
The issue is, it sets the row class as "current_time" when the time is anywhere between the start and end of the hour which isn't what I want.
def date_class(time)
now = DateTime.now.utc
if (now.beginning_of_hour..(now.end_of_hour - 0.5.hours)).cover?(time)
"current_time"
elsif ((now.beginning_of_hour + 0.5.hours)..now.end_of_hour).cover?(time)
"current_time"
elsif (now.beginning_of_day..now.end_of_hour).cover?(time)
"past"
else
"future"
end
end
Any ideas?
The screenshot below and shows that the code works fine and shows true or false correctly.
http://s.deanpcmad.com/2014/uifGf.png
Although it has only been tested with instances of class Time for now, the time_frame gem could be an alternative solution for this kind of problem:
require 'time_frame'
def date_class(time)
now = Time.now.utc
frame = TimeFrame.new(min: now.beginning_of_hour, duration: 29.minutes + 59.seconds)
frame = frame.shift_by(30.minutes) if now.min >= 30
return 'past' if frame.deviation_of(time) < 0.minutes
return 'current_time' if frame.cover?(time)
'future'
end
# Demo: Building 30.minutes interval blocks and print out the date class used by each block:
frame = TimeFrame.new(
min: (Time.now.utc - 2.hours).beginning_of_hour,
max: (Time.now.utc + 2.hours).beginning_of_hour
)
frame.split_by_interval(30.minutes).each do |interval|
puts "#{interval.min} -> #{date_class(interval.min)}"
end
Wouldn't this work for you?
def date_class(time)
now = DateTime.now.utc
return "past" if time < now.beginning_of_hour
return "current_time" if now.hour == time.hour && now.min < 30 && time.min < 30
return "current_time" if now.hour == time.hour && now.min >= 30 && time.min >= 30
return "future"
end
I am sure there is a better way, but this would also work I think

Resources