Ask A Question

Notifications

You’re not receiving notifications from this thread.

CSV Model method and Summing Hours cleanly.

shakycode asked in General

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.

Reply

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.

Reply

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

Reply

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?

Reply

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.

Reply

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.

Reply

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
Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 82,329+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.