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.