Possible to use field in ActiveRecord query? - ruby-on-rails

I have a DateTime in my at field and a number of minutes duration in my minutes field. I want to get all the records where the current time falls within at and at + minutes. Something like:
SpecialEvent.where(at: (DateTime.now - 120.minutes)..DateTime.now)
Except I'm guessing the 120.minutes duration. The exact number of minutes duration for each SpecialEvent is in a minutes field. But I don't know how to use that minutes field within the query...

Some databases (including MySQL) support functions in SQL to perform calculations with dates. This is required to efficiently calculate the timestamp based on data inside the database. Thus, you need to generate correct SQL for your specific database flavour. The example below should work for MySQL. If you use another database, you probably need to adapt this to the available date functions.
SpecialEvent.where(['at >= :now - INTERVAL minutes MINUTE AND at <= :now', now: DateTime.now])
This effectively generates the following SQL query (although with a literal date value instead of NOW():
SELECT * FROM special_events WHERE at >= NOW() - INTERVAL minutes MINUTE AND at <= NOW();

You can format your query like:
SpecialEvent.where(at: (DateTime.now - 120.minutes)..DateTime.now, minutes: '<your search value>')

Related

Rails 5 how to save difference in hour and minutes between two datatime in postgres?

my table has 3 columns: data type timestamp,
|created_At | final_time| duracion(difference between created at and final_time)
| | |
the column difference should save the difference in hours and minutes, in this format HH:MM
this is my controller:
def horario
horario.update(duracion: params[:duracion]) // this params is "00:59"
end
but in the table Horarios, in column duracion i have this:
2017-12-24 03:59:00
so i want to save 00:59 (59 minutes) but postgres save all current date and add 3 hours more.
i want to save so in the future i will be able tu sum column duracion. Or should i change data type for this column? In this case which datatype you recomend me for rails to save HH:MM??
thanks.
Rails 5 supports PostgreSQL's interval type to some extent. You can create interval columns in the usual way and they will be properly represented in db/schema.rb. You can also assign them values in the usual way so you can say things like:
model.some_interval = '6 hours'
and get 06:00:00 inside the database. However, there is nothing in Ruby or Rails that properly represents a time interval (we only have various timestamp and date classes) so when that interval comes out of the database, you'll have a string on your hands, i.e:
> model = Model.find(some_id)
> model.some_interval.class
=> String
so you might end up having to manually parse some strings in Ruby. For simple intervals like '6 hours', this will be easy but it won't be so easy with more complicated intervals like '6 years 23 days 11 hours'.
If you'll only be working with your time intervals inside the database then interval would be natural and easy, you can say things like:
select some_timestamp + some_interval
and
where some_timestamp + some_interval < some_other_timestamp
and everything will work nicely.
However, if you need to work with the intervals back in Ruby then you'd probably be better off storing the interval as a number of seconds in an integer column (or whatever resolution you need). Then you could say things like:
where some_timestamp + (some_interval_in_seconds || 'seconds')::interval < some_other_timestamp
inside the database and
some_time + model.some_interval_in_seconds
back in Ruby.
In any case, strings are probably the wrong approach unless you really like parsing strings everywhere all the time.
As others already pointed out, Rails handles the Postgres Interval type as a string. A string that, unfortunately, is not easy to parse.
If you do this:
u = Users.select('edited_at - created_at as time_dif')
puts u.first['time_dif']
You can get something like 168 days 12:51:20.851115. Ugly right?
Well, using Ruby to convert this string into an useful number is not easy, but you can use Postgres to do the job for you. You will need to do a plain SQL query though, but it's the best method I've found so far:
query = ActiveRecord::Base.connection.execute("
SELECT EXTRACT(epoch FROM time_dif)/3600 as hours_dif
FROM
(
SELECT (edited_at - created_at) as time_dif
FROM users
) AS MainQuery
")
In this example, Postgres' EXTRACT function will convert the Interval type into a number which represents the total seconds of the interval. If you divide this number by 3600 you will get the different in hours as in the example above.
Then, if you want to iterate over the results:
query.each do |r|
puts r['hours_dif']
end
You could save duracion as a float type, where duracion would equal something like final_time - created_at and this value would be the difference in seconds. You can then perform arithmetic with these values and always convert back to minutes, hours, or whatever you need.

Rails query data by hour

I'm having difficulty querying data by an hour for the previous day. Using Rails 4 on Postgres.
eg,
Table X:
created_at, value
timestamp1 3
timestamp2 5
I want to get:
time, value
YYMMDD 00:00 15
YYMMDD 01:00 20
basically the sum per hour. I've tried
Rails & Postgresql: how to group queries by hour?
Is it possible to group by hour/minute/quarter hour directly in ActiveRecord/Rails?
but I still cant figure out how to get it to work properly, i get the wrong hour for replies. How would i set my timezone along with this query?
TableX.group("DATE_trunc('hour',created_at)").count
Thanks!
All you need is create a query like this one:
SELECT date_trunc('hours', created_at), sum(value)
FROM TableX
GROUP BY date_trunc('hours', created_at)
This one uses date_trunc instead of suggested date_part, which fits your question better.

Rails ActiveRecord Query - how to add a certain hour offset to every record in the created_at field in a query

I need to run a group_by query in Ruby on Rails, but I first want to adjust all records in the created_at column by a certain hour amount before running the query. So, for example, adding 9 hours to every record in the created_at field, and then grouping by date.
Something like the following (which is incorrect):
#foo = Bar.group("date(created_at + 9.hours)").count
How can I accomplish this in Rails?
PostgreSQL has excellent support for manipulating dates and times (see Date/Time Functions and Operators). You can express '9 hours' as an interval, add it to a timestamp, and cast to a date:
=> select (now()::timestamp + '9 hours'::interval)::date;
date
------------
2012-09-22
(1 row)
This ends up strikingly similar to your original pseudocode:
#foo = Bar.group("date(created_at + '9 hours'::interval)").count

Using named scope

I have a schedule model having start time as date time and duration in minutes as integer.
Using named scope is there a way I can get the records having (start_time + duration) < current_time ?
You might implement what you asked for with SQL level date manipulation functions, like mysql's DATE_ADD:
scope :not_over, where('DATE_ADD(start_time, INTERVAL duration MINUTE) > UTC_TIMESTAMP()')
Though if performance is a consideration, you might want to precalculate an 'end_time' column and add an index on it.

Rails: find by day of week with timestamp

I need to grab the records for same day of the week for the preceeding X days of the week. There must be a better way to do it than this:
Transaction.find_by_sql "select * from transactions where EXTRACT(DOW from date) = 1 and organisation_id = 4 order by date desc limit 7"
It gets me what I need but is Postgres specific and not very "Rails-y". Date is a timestamp.
Anyone got suggestions?
How many days do you want to go back?
I have written a gem called by_star that has a dynamic finder suited for finding up to a certain number of days in the past. If the number of days was always a number you could use this finder:
Transaction.as_of_3_days_ago
If it was dynamic then I would recommend using something such as future or between, depending on if you have transactions in the future (i.e. time travel):
Transaction.future(params[:start_date].to_time)
Transaction.between(params[:start_date].to_time, Time.now)
AFAIK Rails has no any methods to do this by other way. So best, and faster, solution - build DOW index on date column and use your query.

Resources