K. Eclipse
K. Eclipse

Reputation: 3

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:

  1. Date: "1/1/2021", Hours: "01:00"
  2. 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!

Upvotes: 0

Views: 461

Answers (1)

JohnP
JohnP

Reputation: 1309

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.

Upvotes: 0

Related Questions