Tested CSV export using Rails 3

by-web development

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

Post
  * Title
  * Body
  * Created_at
  * :author

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]
     end

     subject { @post.to_csv }

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

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

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

  end

  ...

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| self.author[f] }
  end

end

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)
  end

  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
  end
end

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 << @posts.map { |post| post.to_csv }.flatten
      end

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

  end

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

Thanks for reading. To continue the discussion contact me: or

Do you need a team to build your web/mobile app? Are you looking for some great Ruby on Rails developers?
Send us an email now at hello@cookieshq.co.uk

Related posts