CSV Model method and Summing Hours cleanly.
I've made a bit of a mess with my timeclock functionality. Basically what I want to do is pass all of the records in a given search range (which I've already written a model method for and it's working), merge and persist the params into the controller and pass it to a to a to_csv method in the model.
Here's what my controller action looks like with the respond_to block.
def index
@clock_event = current_user.clock_event
@user = current_user
@clock_events = current_user.clock_events.complete.search(params[:search])
respond_to do |format|
format.html do
@clock_events = @clock_events.paginate(:per_page => params[:per_page] || 5, :page => params[:page]).order('created_at DESC')
end
format.csv { send_data ClockEvent.to_csv(@clock_events) }
end
This works fine and dandy, no problems. Now take a look at the model method.
def self.to_csv(records = [], options = {})
CSV.generate(options) do |csv|
csv << ["Employee", "Clock-In", "Clock-Out", "Station", "Comment", "Total Shift Hours", "Total Hours: #{TimeFormatter.format_time(records.sum(&:total_hours))}"]
records.each do |ce|
csv << [ce.user.try(:username), ce.formatted_clock_in, ce.formatted_clock_out, ce.station.try(:station_name), ce.comment, TimeFormatter.format_time(ce.total_hours)]
end
end
end
Since I was shoveling an array into the csv object I really wasn't sure on where to put the total sum of each clock event to calculate the value. So I made a dirty hack by interpolating the object and putting it into the csv header. Is there a better way to do this where I can have the total sum of records
display at the bottom of the CSV file? I'm sure this is simple but I'm just not getting it.
Just shovel those onto the csv outside of the records loop. Pass in the values you want and they should show up at the bottom.
Lol thanks for the tip, but I already figured that out and got it working. Sorry I didn't post a reply, been swamped. Here's what my method looks like now.
def self.to_csv(records = [], options = {})
CSV.generate(options) do |csv|
csv << ["Employee", "Clock-In", "Clock-Out", "Station", "Comment", "Total Shift Hours"]
records.each do |ce|
csv << [ce.user.try(:username), ce.formatted_clock_in, ce.formatted_clock_out, ce.station.try(:station_name), ce.comment, TimeFormatter.format_time(ce.total_hours)]
end
csv << [TimeFormatter.format_time(records.sum(&:total_hours))]
end
end
Works like a champ :P
Ok, now I want to figure out how to show a total hours for each user inside of the block on this CSV method. Since it's iterating over every ce
object and calling different fields, that's fine. But I'd like to figure out how to export to csv, sort by user.username (done), and after each user's clock events add a total for each user.
I'm thinking I can run another block inside of this method but unsure on how to wire it up. Thoughts?
Ok, I've partially figured this out.
def self.to_csv(records = [], options = {})
CSV.generate(options) do |csv|
csv << ["Employee", "Clock-In", "Clock-Out", "Station", "Comment", "Total Shift Hours"]
records.each do |ce|
csv << [ce.user.try(:username), ce.formatted_clock_in, ce.formatted_clock_out, ce.station.try(:station_name), ce.comment, TimeFormatter.format_time(ce.total_hours)]
csv << [TimeFormatter.format_time(ce.user.clock_events.sum(&:total_hours))]
end
csv << [TimeFormatter.format_time(records.sum(&:total_hours))]
end
end
I shovel the user's clock_events based off of the ce object and sum the total hours into the csv object. This gives me the total time per employee in CSV, but since it's in the block and iterating it repeats it every other line. I have a feeling I'm very close, but for some reason doing this inside of a block doesn't seem right to me. Back to the drawing board.
Ok so after looking at this, I realized I was in error.
Getting the users's time totals like this: csv << [TimeFormatter.format_time(ce.user.clock_events.sum(&:total_hours))]
is wrong.
Because I'm going back up the association in reverse and pulling all of their clock_events and then summing, instead of using what the records array is passing in from the controller.
So it's time to figure this out.
With some help this morning from Chris and me reading APIDocs on filtering arrays we came to this method which works right. :)
def self.to_csv(records = [], options = {})
CSV.generate(options) do |csv|
csv << ["Employee", "Clock-In", "Clock-Out", "Station", "Comment", "Total Shift Hours"]
records.each do |ce|
csv << [ce.user.try(:username), ce.formatted_clock_in, ce.formatted_clock_out, ce.station.try(:station_name), ce.comment, TimeFormatter.format_time(ce.total_hours)]
end
records.map(&:user).uniq.each do |user|
csv << ["Total Hours for: #{user.username}"]
csv << [TimeFormatter.format_time(records.select{ |r| r.user == user}.sum(&:total_hours))]
end
csv << ["Total Payroll Hours"]
csv << [TimeFormatter.format_time(records.sum(&:total_hours))]
end
end