How to create and test s CSV export using Rails 3, and test it using Rspec.

Right, let say that we have some models with this structure

  * Title
  * Body
  * Created_at
  * :author

  * First_name
  * Last_name
  * Email

And our client wants to be able to download a CSV file containing all Posts information, with it’s author data.

First, let’s start by creating the Post model spec, which would be something like:

require 'spec_helper'

describe Post do


  Describe "exporting a post .to_csv" do

     before do 
        @author = [create author]
        @post    = [create post or factory, with author]

     subject { @post.to_csv }

     its(:length) { should equal(7) }

     [:title, :body, :created_at, :author_id].each do |field|
       it { should include @post[field] }

     [:first_name, :last_name, :email].each do |field|
       it { should include @author[field] }



Right that should gives us enough to start coding now. Let’s go to the Post model

class Post < ActiveRecord::Base

  def to_csv
    csv = []
    csv += [:title, :body, :created_at].map { |f| self[f] }
    csv += [:first_name, :last_name, :email].map { |f|[f] }


This should makes our previous test pass. Now it’s time to export the CSV in the browser.

First, let’s write some acceptance tests that will look like:

require 'acceptance/acceptance_helper'

feature "Downloading a post CSV export" do

  background do
    @post = FactoryGirl.create(:post_with_author)

  scenario "Downloading CSV file" do
    require 'csv'
    visit export_to_csv_posts_path
    csv = CSV.parse(page.text)
    csv.first.should == ["Title", "Body", "Created_at", "First name","Last Name", "Email"]
    post_line = CSV.parse(@post.to_csv.join(',')).first
    csv.should include post_line

Easy enough, we are using Capybara page.text to receive the content of the page and parsing it using the same CSV parser as in our controller, and we are testing that we have our Headers in place, and that our post line exist.

Now let’s create our export_to_csv method in the Post controller

class PostsController < ApplicationController

    def export_to_csv
      require 'csv'

      @posts = Post.includes(:author)

      csv = CSV.generate(:force_quotes => true) do |line|
        line <<["Title", "Body", "Created_at", "First name","Last Name", "Email"]
        line << { |post| post.to_csv }.flatten

      send_data csv,
          :type => 'text/csv; charset=iso-8859-1; header=present',
          :disposition => "attachment; filename=post-#{'%d-%m-%y--%H-%M')}.csv"


And that should make our acceptance test pass as well, and you now have an easy and expandable CSV generation.