How to download a CSV file in Ruby on Rails?
Asked Answered
B

5

22

In my InvoicesController I have this:

def index
  @invoices = current_user.invoices
  respond_to do |format|
    format.html
    format.xls
    format.csv # not working!
  end
end

In my index.html.erb view I have these two download links:

<%= link_to "Download as Excel", invoices_path(:format => "xsl") %>
<%= link_to "Download as CSV", invoices_path(:format => "csv") %>

The templates index.xsl.erb and index.csv.erb do exist as well.

The first link works, i.e. the Excel file gets downloaded to the user's computer. However, the CSV file is rendered in the browser and not downloaded.

What must I do to enable users to download CSV files as well?

Thanks for any help.

Bueschel answered 9/6, 2013 at 22:45 Comment(1)
Have you required Ruby's native CSV library in an initializer yet?Republican
R
33

Try specifying the appropriate content headers and explicitly rendering your index.csv.erb template in your format.csv handler block.

# app/controllers/invoices_controller.rb
format.csv do
    response.headers['Content-Type'] = 'text/csv'
    response.headers['Content-Disposition'] = 'attachment; filename=invoice.csv'    
    render :template => "path/to/index.csv.erb"
end
Republican answered 10/6, 2013 at 9:38 Comment(3)
Thanks! That did the trick. It seems that you really have to explicitly specify the template name, even though it took me a while to get the syntax right: render "#{Rails.root}/app/views/invoices/index.csv.erb" I changed your answer accordingly.Bueschel
Glad to hear it worked. It's a pedantic point, but can you verify whether render :template => 'invoices/index' (without the path) works as well? It should, AFAIK, and I'll update my answer accordingly.Republican
I had to also add :layout => falseHessney
K
6

Try this

format.csv do
  response.headers["Content-Type"] = "text/csv; charset=UTF-8; header=present"
  response.headers["Content-Disposition"] = "attachment; filename=invoices.csv"
end
Kick answered 9/6, 2013 at 23:1 Comment(1)
Thanks! I tried that but it's not downloading or even displaying the file in the browser. Nothing happens when I click on the link. I added the content of my index.csv.erb file above as well.Bueschel
B
2

I recently discovered

render_csv

maybe check out this railscast (yay)

Baneful answered 23/5, 2014 at 22:58 Comment(0)
N
2

ERB is mostly meant for generating unstructured data formats and markup. I would personally not use it to generate CSV, that's what that the built-in CSV library is for.

In more recent versions of Rails there is an output stream, which allows you do write to the output directly to reduce overall memory usage.

So, maybe something like this?

require 'csv'

class BooksController
  def index
    response.content_type = Mime[:csv]
    response.headers['Content-Disposition'] = content_disposition
    # Write directly to the response stream so the client can start reading
    # immediately when the web server starts flushing the buffer.
    response.stream.write CSV.generate_line(header)
    # Use find_each to prevent loading all records into memory.
    Book.find_each.each do |book|
      # Immediately write rows to the response stream to reduce memory usage.
      response.stream.write CSV.generate_line(row(book)) }
    end
  ensure
    response.stream.close
  end

  private

  def header
    # Rely on the model and I18n to format attribute names.
    %i[id title updated].map do |attribute_name|
      Book.human_attribute_name(attribute_name)
    end
  end

  def row(book)
    [book.id, book.title, book.updated_at.iso8601]
  end

  def content_disposition
    # Use ContentDisposition to allow characters in the filename which are
    # normally not allowed in an HTTP header value (ie. all non-ASCII and
    # certain control characters).
    ActionDispatch::Http::ContentDisposition.format(
      disposition: 'attachment', filename: "#{content_filename}.csv"
    )
  end

  def content_filename
    "All books present at the café in #{Time.zone.today.year}"
  end
end
Neoplasm answered 6/3, 2023 at 13:40 Comment(0)
S
0

I found other solution that worked for me (Rails 3.2 and Ruby 2.3.3)

  1. After generating the csv
report_csv = CSV.generate(col_sep: ";")...
  1. It can be downloaded from controller:
  • Inside the block format.csv we could use
send_data(report_csv, filename: 'banana.csv')
  1. And we could access this action from a link that has the target blank
Souvenir answered 3/11, 2021 at 22:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.