Peter Marklund

Peter Marklund's Home

Wed Aug 16 2006 01:08:00 GMT+0000 (Coordinated Universal Time)

Rails Recipe: CSV Export

A common requirement from customers is the ability to export tabular data to a CSV file that can be imported into Excel. Ruby on Rails uses the standard Ruby CSV library to import test fixtures that are in CSV format. However, there is a runner up CSV library called FasterCSV which as the name suggests is faster and also has a much cleaner API. I was hesitant to use FasterCSV in production since it is still in Beta but after finding the discussion about including FasterCSV in the Ruby standard library I decided to give it a try.

The first step was to download the latest FasterCSV tgz file and extract into RAILS_HOME/vendor/fastercsv. I then added the following line to config/environment.rb:

require 'fastercsv/lib/faster_csv'

The action that exports the CSV data looks roughly like the following:

  def csv
    @list = @group.lists.find(params[:id])

    csv_string = FasterCSV.generate do |csv|
      csv << ["Type", "Name", ... more attribute names here ... ]

      @list.records.each do |record|
        csv << [record['type'],
                record['name'],
                ... more values here ...]
      end
    end

    filename = @list.name.downcase.gsub(/[^0-9a-z]/, "_") + ".csv"
    send_data(csv_string,
      :type => 'text/csv; charset=utf-8; header=present',
      :filename => filename)
  end

In the corresponding controller test I check that the CSV response can be parsed with the other CSV parser, that there is a header row with expected attributes, that the number of data rows is correct, and finally that one of the rows has the correct values:

  def test_csv
    get_success :csv, :id => lists(:peterlist).id

    reader = CSV::Reader.create(@response.body)

    header = reader.shift
    ['Type', 'Name', ... more attributes here ...].each do |attribute|
      assert header.include?(attribute)
    end

    rows = []
    reader.each do |row|
      rows << row
    end
    
    assert_equal rows.size, lists(:peterlist).size
    item = lists(:peterlist).items.first
    item_row = rows.select { |row| row[1] == item.contact.name }.first
    assert_equal item_row[0], 'Contact'
    assert_equal item_row[1], item.name
    ... assertions for the other attribute values here ...
  end

To see what CSV export from a controller looks like with the Ruby CSV library - check out this blog post.