In Rails, how can I submit a form and request a csv formatted page?
Asked Answered
D

3

7

I have a few parts of the solution, but I'm having trouble bringing them together.

I have a page with two text fields (in a form_tag) in which I'll enter a datetime string with the start and end dates of the records I want to download in CSV form.

I can use a submit_tag and get the two dates, but then I don't know how to get the view to tell the controller that I want a CSV, so . I can use a link_to, but then the params get left behind.

The view and controller look a little wonky as I'm trying to figure out how this stuff should work together. I won't ship both a link and a button, for example. I also removed/changed things as needed for brevity/privacy.

show.html.erb:

<%= form_tag do %>    
  <br/><br/>
  <%= label_tag :start_date, "From:" %>
  <%= text_field_tag :start_date, nil, size: 40 %>
  <%= label_tag :end_date, "To:" %>
  <%= text_field_tag :end_date, nil, size: 40 %>
  <br/>

  <%= link_to "Export Report", report_path(:csv) %>

  <%= submit_tag("Generate .CSV", format: :csv) %><br/><br/>
<% end %>

report_controller.rb:

require 'csv'

class ReportController < ApplicationController
  def show
    if params[:start_date]          
      @data = get_data(params[:start_date], params[:end_date])
      respond_to do |format|
        format.html
        format.csv
      end
    end
  end

  def build_csv_enumerator(header, data)
    Enumerator.new do |y|
      CSVBuilder.new(header, data, y)
    end
  end

  def download
    if params[:start_date]
      @data = get_data(params[:start_date], params[:end_date])
      respond_to do |format|
        format.html
        format.csv
      end
    end
    redirect_to action: "show"
  end

  private def csv_filename
    "report-#{Time.now.to_s}.csv"
  end
end

class CSVBuilder

  attr_accessor :output, :header, :data

  def initialize(header, data, output = "")
    @output = output
    @header = header
    @data = data
  end

  def build
    output << CSV.generate_line(header)
    data.each do |row|
      output << CSV.generate_line(row)
    end
    output
  end
end

download.csv.erb:

<%- headers = ["name", "email", "created_at"] -%>
<%= CSV.generate_line(headers) %>
<%- @data.each do |line| -%>
  <%- row = line.values_at(*headers) -%>
  <%= CSV.generate_line(row) %>
<%- end -%>
Dilettante answered 29/6, 2017 at 0:49 Comment(0)
T
4

Because you want to download a CSV, the request should be a GET request.

Your link should look like <a download href="/reports/1.csv?from=2017-01-01&to=2017-12-31">Download</a>. I suggest you build this url on browsers (which means using javascript, not rails).

Your rails application must understand the extension .csv. This can be configured in config/initializers/mime_types.rb

Mime::Type.register "text/csv", :csv

Now you can send CSV to the browser

class ReportsController < ApplicationController
  def show
    respond_to do |format|
      # ...
      format.csv do
        @csv = ...
        send_data @csv.to_s  # what you send must be a string
      end
    end
  end
end
Typescript answered 29/6, 2017 at 2:23 Comment(0)
A
1

Links are self-contained; submit buttons (typically) submit forms (with all their non-disabled, name-having values) to the action URL of the form (unless you're doing submission through JavaScript, in which case you can do whatever you want, obviously).

Thus, you don't specify the URL on submit_tag, you specify it on form_tag:

Starts a form tag that points the action to a url configured with url_for_options just like ActionController::Base#url_for.

Ammoniate answered 29/6, 2017 at 1:58 Comment(0)
V
0

As one of the answers mentioned, you need to specify the format on the form_tag. It can get tricky as the format needs to be set in the url_options, not in the other form_tag options. For example:

<%= form_tag({controller: 'my_controller', method: 'my_method', format: 'csv'}, method: "get") do %>
  ...fields
  <%= submit_tag 'Submit' %>
<% end %>
Vulgus answered 7/6, 2019 at 16:22 Comment(1)
I was laid off from the job where I might have used (or been able to verify) your answer nearly 2 years ago, but I hope your answer is of use to someone who finds this question.Dilettante

© 2022 - 2024 — McMap. All rights reserved.