Exporting CSV, PDF, XLS, XML, VCF in Rails


CSV Format

To save database information as a csv, we will need to require the rails csv library in the controller we are going to use it in. You also need to add the format in your view method you need it in.

# UsersController.rb
require 'csv'

def index
  # Your Code
  @users = User.all

  respond_to do |format|
    format.html
    format.csv
  end
end

Then in your index.csv.erb view file, you will need to add in the rows and columns you want to be displayed in the csv file.

# index.csv.erb
# This first line will be the column headers
["First Name", "Last Name", "Join Date", "Login Count"].to_csv

# This loop iteration will then be all the rows
<% @users.each do |user| %> 
  <%= [user.first_name, user.last_name, user.join_date, user.login_count].to_csv %>
<% end %>

Then in your users index.html.erb add the following line.

# users/index.html.erb
<%= link_to "CSV", users_path(format: "csv") %>

Another way of doing it would be to add a self.to_csv method in your model, and then call it directly from the controller when its path is called.

# UsersController.rb
def index
  @users = User.all
  respond_to do |format|
    format.html
    format.csv { send_data @users.to_csv }
  end
end
# models/user.rb
def self.to_csv(options = {})
  CSV.generate(options) do |csv|
    csv << column_names
    all.each do |user|
      csv << user.attributes.values_at(*column_names)
    end
  end
end

XLS Format

To convert your data to XLS, you'll need to add an xls view file to your users directory. Then paste the following code inside.

# users.xls.erb
<?xml version="1.0"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
  xmlns:o="urn:schemas-microsoft-com:office:office"
  xmlns:x="urn:schemas-microsoft-com:office:excel"
  xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
  xmlns:html="http://www.w3.org/TR/REC-html40">
  <Worksheet ss:Name="Sheet1">
    <Table>
      <Row>
        <Cell><Data ss:Type="String">First Name</Data></Cell>
        <Cell><Data ss:Type="String">Last Name</Data></Cell>
        <Cell><Data ss:Type="String">Join Date</Data></Cell>
        <Cell><Data ss:Type="String">Login Count</Data></Cell>
      </Row>
    <% @users.each do |user| %>
      <Row>
        <Cell><Data ss:Type="Number"><%= user.first_name %></Data></Cell>
        <Cell><Data ss:Type="String"><%= user.last_name %></Data></Cell>
        <Cell><Data ss:Type="String"><%= user.join_date %></Data></Cell>
        <Cell><Data ss:Type="Number"><%= user.login_count %></Data></Cell>
      </Row>
    <% end %>
    </Table>
  </Worksheet>
</Workbook>

After you do that, then add the file format in its controller.

# UsersController.rb
def index
  # Your code

  respond_to do |format|
    # Append the following line to this method
    format.xls
  end
end

All that's left now is to add the link to your view file you need it in.

<%= link_to "Excel", users_path(format: "xls") %>

Another way to accomplish this is through the AXLSX Rails Gem. The method below will also create a new sheet in and workbook for each user. You would want to add in additional information for perhaps user posts, comments, pictures, or whatever needs to be included.

# controllers/users_controller.rb
def index
  @users = User.all

  respond_to do |format|
    format.xlsx 
  end
end
# views/user/index.xlsx.axlsx
@wb = xlsx_package.workbook
@users.each do |user|
  @wb.add_worksheet(name: user.name) do |sheet|
    sheet.add_row ["First Name", "Last Name", "Join Date", "Login Count"]
    sheet.add_row [user.first_name, user.last_name, user.join_date, user.login_count]

    # User posts?
    # user.posts.each do |post|
    #   sheet.add_row [post.topic, post.title, post.body, post.comment_count]
    # end

    sheet.column_widths 10, nil, nil, nil
  end
end

An additional guide can be found here.

PDF Format

To get started we will be using Wicked PDF. We will first need to add this to our gemfile.

# Gemfile
gem 'wicked_pdf'

The next step is optional, you can add this mime type to your config/initializers/mime_types.rb.

# config/initializers/mime_types.rb
Mime::Type.register "application/pdf", :pdf

You can also turn any page into a pdf without a view file by adding middleware to your config/application.rb.

# config/application.rb
config.middleware.use WickedPdf::Middleware

Next, we will need to add the pdf format to our respond_to method in the controller we want to use it in.

# controllers/UsersController.rb
class UsersController < ApplicationController
  def index
    @users = User.all
    respond_to do |format|
      format.html
      format.pdf do
        render :pdf => "report", :layout => 'pdf.html.slim'
      end
    end
  end
end

Next we can also create a layout file to use for when our pdfs are called using Wicked PDFs helpers.

# app/views/layouts/pdf.html.slim
doctype html
  head
    title My Rails Pdf
    = wicked_pdf_stylesheet_link_tag    "application", :media => "all"
    = wicked_pdf_javascript_include_tag "application"
    = csrf_meta_tags
  body
    == yield

We will then need to create a view file in the view directory we need it in.

# app/views/users/index.pdf.slim
.container
  .row
    = wicked_pdf_image_tag('my-logo.png')
  .row
    .col-xs-6
      h3
        My Cool Header
      p
        Some cool stuff
    .col-xs-6
      h3
        Your header
      p
        Some paragraph text
  .row
    table.table.table-striped
      thead
        th #
        th Name
        th Join Date
      tbody
        - @users.each do |user|
          tr
            td
              = user.id
            td
              = user.name
            td
              = user.join_date

In the above code block, it is obvious we are using Wicked PDFs helpers instead of the normal rails helpers. This is because if we were to use the rails helpers, the application would hang upon request until completed, and also any img helpers would not be loaded.

A little side note apart from this is that wkhtmltopdf is run outside our rails application. If we need to add any files to it, it needs to be an absolute path of that file or it will not be found.

If you need to send it in a mailer, you can use the below line in your *_mailer.rb.

def mailer_method(*args)
  attachments["name.pdf"] = WickedPdf.new.pdf_from_string(render_to_string(pdf: "method_name", template: "path/to/template.pdf.html.slim"))
  mail(:to => "", :from => "", :subject => "")
end

VCF Format

Lets start off by creating our .vcf view file and add some code into it.

# users.vcf.erb
<% @users.each do |user| %>
  <%= "BEGIN:VCARD" %>
  <%= "VERSION:4.0" %>
  <%= "N:#{user.first_name};#{user.last_name};;;"%>
  <%= "FN:#{user.full_name}" %>
  <%= "PHOTO;TYPE=uri:#{user.avatar}" %>
  <%= "EMAIL;TYPE=work,internet;PREF=1:#{user.email}" %>
  <%= "item1.URL:#{user.website_url}" %>
  <%= "item1.LABEL:#{user.website_type}" %>
  <%= "END:VCARD" %>
<% end  %>

Now that we have that finished, all we need to do is add this to our controller index method in the respond_to method.

# UsersController.rb
def index
  # Your code

  respond_to do |format|
    # Append the following line to this method
    format.vcf
  end
end

XML

Setup all the information you need to export to a view in your controller and then add the following line to its method.

# controllers/pages_controller.rb
def xml_sitemap
  @blogs = Blog.all

  respond_to do |format|
    format.xml
  end
end

Then add this to your view file.

# views/pages/xml_sitemap.xml.slim
doctype xml

urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"

  url
    loc 
      | https://www.samwholst.com/
    priority 
      | 1.00
  url
    loc
      | https://www.samwholst.com/blog
    priority
      | 0.80

  - @blogs.each do |blog|
      url
        loc 
          = blogs_url(blog)
        priority
          | 0.70