order by created_at plus another value - ruby-on-rails

I have a table which contains a column "discussion_duration" where I am storing certain times in seconds (6 hours = 21600). I would like to sort this table by 'created_at' + 'discussion_duration'.
I can add created_at and discussion_duration in ruby and it returns a date. However since the parameters in the rails .order() is raw sql, I'm not sure how to add these two fields then sort by the output.
Any help would be much appreciated.

edit: Actually this is probably what you're looking for. You'll need to use your database specific date/time functions to do it and here's a mysql example.
.order('TIMESTAMPADD(SECOND, discussion_duration, created_at)')

Related

Rails - Convert values in a column of time data type to seconds and get their sum

I'm quite new to rails so I'm not really familiar with most of its methods etc.
I have this table in my database called "logs" with columns "date" (date) and "hours" (time). I wanted to get all the logs with the same date and then get their total hours converted to seconds.
Let's say I have two existing logs:
Date: "1/1/2021", Hours: "01:00"
Date: "1/1/2021", Hours: "04:00"
Since they have the same date, I'm expecting to get the sum of "01:00" and "04:00" both converted to seconds.
My log model so far contains this:
scope :on_this_day, ->(date) { where(date: date) }
and I used it for this which is in my service:
#log = Log.new(#log_params)
total = Log.on_this_day(#log.date).sum(Time.parse(:hours).utc.seconds_since_midnight) ==> # supposedly the total hours (in seconds) on a particular day
Right now the error is telling me there is no implicit conversion of Symbol into String. I'm guessing it's because of using ":hours" inside Time.parse().
I don't know what's a good approach for this. I'd appreciate some help. Thank you!
OK, so there's quite a lot going on here. First of all, you don't need to add Date and Hours fields to your model, because all models automatically get created_at and updated_at fields added (at least, if you follow the recommended way to write your migrations), and it's best to use these.
Whichever field you use to get the creation time, your scope looks fine. The issue, as you saw, is in how you use it. Scopes return an ActiveRecord::Relation object, which is a collection of records. So, your Log.on_this_day(#log.date) call will return a collection, and you can't just sum a collection. You need to work with an individual field from each record in the collection.
Then, you're not using Time.parse correctly. This expects a text string that encodes a date and returns a Time object. You're passing a symbol (:hours), which is therefore raising an error. So, you would need to do something like Time.parse('2021-08-15'). One way to debug this sort of command is to use it at the console (rails c) so you can get a feel for how it works.
Ultimately, you're going to need to break this down a bit, and do something like this:
total = Log.on_this_day(#log.date).reduce do |sum, log_record|
sum + Time.parse(log_record.hours.to_s).utc.seconds_since_midnight
end
(If you're not familiar with the #reduce method yet, it iterates over an enumerable like an array or collection, and reduces it to a single value (here, a sum) based on the calculation provided in the block, which is carried out on each record. You can do something similar with #each but it's more complicated.)
But, even then, you're over-complicating things because, if you use a field that has a Time type like created_at, you don't need to parse it at all:
total = Log.on_this_day(#log.date).reduce do |sum, log_record|
sum + log_record.created_at.utc.seconds_since_midnight
end
This takes the collection returned by your scope from the Log table, and then iterates over each log_record within that collection. It takes the created_at value and adds to the sum (initially zero) the number of seconds since midnight, and then moves to the next record and adds its number of seconds, and so on.
Hopefully, that gets you where you need to be! Basically, try out the individual steps at the console to make sure you understand what they're doing before combining them, and you'll probably find it easier to get things working.

How to check if there are blank period of posts

I want to check if there was a blank period that have no posts.
I wrote like this:
Post.all.pluck(:date).sort.each_cons(2).any?{|i,j| (i - j).abs > 7 }
Is there a better implementation?
I'm not good at database functions. Is there a function for it in Postgresql.
Albeit expensive, you could do a cross join (which really is a cartesian join) and calculate directly in the database. Also, here I assume that the columns are Datetime fields instead of Date.
For MySQL
Post.
joins('CROSS JOIN posts p2').
where('TIMESTAMPDIFF(DAY, date, p2.date) > 7').
present?
Note that the difference needed to be converted to days if you want 7 days period.
For Postgres, do check out the relevant function at the postgres datetime docs. You might have to do something like this:
EXTRACT(DAY FROM ABS(d1 - d2))

Finding records created 10 days ago

My initial thought was:
user.humans.where("created_at = ?", 10.days.ago)
Though, this seems to be looking for the record created 10 days ago at the exact time when the statement is called. I want to collect the records created on that day, regardless of their time.
Is anyone aware of a convenient way to do this? Let me know if I need to elaborate.
Thanks.
You'll probably want to use a range here, as I assume this is a datetime column.
User.humans.where("? <= created_at AND created_at <= ?", 10.days.ago.beginning_of_day, 10.days.ago.end_of_day)
You'll also want to make sure you're setting the time zone of your Rails application so that you're explicit about which time period you consider to be the 10th day.
Whichever DBMS you are using will have a method to convert a datetime to a date. You should then compare this to a date in ruby. For example, if your DBMS is MySQL you could say
user.humans.where("date(created_at) = ?", 10.days.ago.to_date)
If you're not using MySQL then you should be able to google converting a datetime to a date in your DBMS of choice.

Configuring Ruby-on-rails created_at and updated_at dates time_zone

Rails stores created_at and updated_at timestamps in UTC time.
But I am using these fields to filter and store (and do a lot of other stuff with the records) based on these fields, so it's important that when I call created_at and updated_at attributes, I get timestamps in my time zone.
As told by the following two SO questions, I can do that by configuring the time zone in the environment.rb file.
However, that's now working for me.
Because I am fetching the records from the database, based on created_at fields (which are in UTC) so naturally wrong ones are coming out, and then I am displaying them, so the dates are displayed in UTC, again, not what I want.
Is there a way to change in what time-zones the rails stores the dates?
OR
Is there a workaround for how I can achieve the database-fetching and displaying, without making a call to Rails object.created_at attribute?
Rails and timezone in created_at
Rails Time zone issue
I wonder why I even asked the question on this forum, because the workaround seemed to be pretty simple. But it did take me eight hours to reach it :)
1) While fetching records from the database and displaying them, I could simply do
select CONVERT_TZ(date) as date from table;
instead of doing
select date from table;
CONVERT_TZ() is a mysql specific function, so it worked for me. Not a universal solution though.
2) Secondly, when I had to fetch the records from a date range given in any other time-zone, I could do one of the two things.
One - I could replace [user-entered-date] 00:00:00 with [user-entered-date] 07:00:00 for the date-range's starting point and [user-entered-date] 59:59:59 with [user-entered-date + 1.day] 07:00:00 for the range's ending point. Note this would require me to use DateTime objects instead of using Date objects.
Two - In the where clause, I could do
CONVERT_TZ(registrations.created_at) <= [user/entered/date]
instead of
(registrations.created_at) <= [user/entered/date]

How to have rails to store time in int format for created_at field automatically

I use rails for version 2.3.5, when i update or save a record, the created_at and updated_at fields can be filled in automatically in the format like '2011-05-17 23:54:53', however i want to store the second int value of time to these fields automatically, what should i do?
Thank you in advance.
Within your database engine the storage method for dates, times, datetimes, and timestamps can vary but the presentation through the SQL layer is often similar. You can transform the request from integer to time if you like using some of the built in functions.
For example, in MySQL you can use the UNIX_TIMESTAMP function to return integer times:
SELECT UNIX_TIMESTAMP(created_at) FROM users
You can also insert integers and convert to times:
UPDATE users SET updated_at=FROM_UNIXTIME(1111885200) WHERE id=1
This sort of thing is not supported natively within ActiveRecord, but you can do conversions using the DateTime class:
User.first.created_at.to_i
You can also convert from an integer to a DateTime:
user.updated_at = Time.at(1111885200)

Resources