How to email reports from Rails

Would you like to have a simple and painless way of sending reports from your Rails application via email? Any substantial web application will require some reporting, and the e-commerce application I'm working on is no exception. I recently received a request to send a daily report of the previous day's sales via email. Here's one way to do it quickly and easily.

The actual report generation is in a class I set aside specifically for reports called Report. It does some SQL tricks to pull out nicely-formatted data for the date of interest. All I have to worry about at that point is sending that data every day via email. Here's my notifier code:

class Notifier < ActionMailer::Base def sales_for_yesterday require 'FasterCSV'

@from = '' @recipients = '' @sent_on = @yesterday = @body = { :yesterday => @yesterday } @subject = "Sales Report"

attachment :content_type => "text/csv", :filename => "sales_#{@yesterday.to_date}.csv" do |a| a.body = FasterCSV.generate do |csv| csv < < (fields = ["artist", "product", "variant", "unit price", "qty sold", "total"]).map {|f| f.titleize } Report.sales_for_date(@yesterday).each do |row| csv << {|f| row[f] } end end end end end

The fun part is the attachment generation. The Rails API documentation doesn't explain all the options you can send the attachment method — it simply shows you how to pass the desired content-type as a text argument. My example shows you how to specify the file name for the attached file by passing in a hash of options to the attachment method. I use the block to add the content to the attachment on the fly, generating it with FasterCSV. This wouldn't work as well with huge amounts of data, since it would all be in memory, but for a daily report it's just fine.

I generate the CSV file by picking the fields of interest from the report, prettying them up with titleize, then making that the first row of the file. The rest of the rows are created by iterating over the results from a method in my Report class and then iterating over the field list to make sure the data gets populated in the correct column order.

The only thing left to do is invoke the mailer every day. I do that with a one-line cron job that is executed daily:

RAILS_ENV=production /path/to/app/script/runner Notifier.deliver_sales_for_yesterday

Easy as pie.