Rails - CSV export: prompt for file download - ruby-on-rails

I want to give my users the ability to export a table to CSV.
So in my controller, I've added on top of the file:
respond_to :html, :js, :csv
I'm also setting the headers if the requested format is csv:
if params[:format] == 'csv'
generate_csv_headers("negotiations-#{Time.now.strftime("%Y%m%d")}")
end
Code for generate_csv_headers(in application_controller) is:
def generate_csv_headers(filename)
headers.merge!({
'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
'Content-Type' => 'text/csv',
'Content-Disposition' => "attachment; filename=\"#{filename}\"",
'Content-Transfer-Encoding' => 'binary'
})
end
I've also created a view named index.csv.erb to generate my file:
<%- headers = ["Id", "Name"] -%>
<%= CSV.generate_line headers %>
<%- #negotiations.each do |n| -%>
<%- row = [ n.id,
n.name ] -%>
<%= CSV.generate_line row %>
<%- end -%>
I don't have any error, but it simply displays the content of the CSV file, while I'd expect a prompt from the browser to download the file.
I've read a lot, but could not find anything that'd work.
Do you have an idea?
thanks, p.

I'm still unsure about why this fixed the issue, but it did.
I changed the link in the view to
<%= link_to "Export to csv", request.parameters.merge({:format => :csv})%>
and it now works!

I'm not sure you have to do it this way, but if you generate and save the file, you can use send_file to send it to the browser. See http://api.rubyonrails.org/classes/ActionController/Streaming.html

You might be interested in this gem I made called CSV shaper that allows you to create CSV output using a really nice Ruby DSL.
It will also handle setting the response headers correctly, while allowing filename customisaton.
https://github.com/paulspringett/csv_shaper

Related

Rails Controller - Cant load such file (CSV)

I have the following in my view:
<%= form_tag import_list_path, multipart: true do %>
<%= file_field_tag(:file) %>
<%= submit_tag(:Submit) %>
<% end %>
I have this in my controller:
def import
require 'csv'
csv = CSV.load params[:file].tempfile.read
CSV.new(csv.tempfile, :col_sep => ",", :return_headers => false).each do |column|
name_array << column[5]
end
redirect_to(:index)
end
I'm just trying to store a temporary CSV file in memory and do some actions on it, essentially using it to pull in information to be used in consuming a web service later.
This is the error I receive:
cannot load such file -- Column1,Column2,Column3,Column4,Column5,Column6,Column7,etc....
How can I change my controller to not throw this error?
That should do it.
def import
require 'csv'
CSV.new(params[:file].tempfile, :col_sep => ",", :return_headers => false).each do |column|
name_array << column[5]
end
redirect_to(:index)
end
Another notice: Dont put your logic in your controller it belongs in the model ;)
//
That menas you should write a method in your model that deals with the data and only refere the path to the csv file as a parameter of the method. The model is there as an interface between you app and the database and for the things done in the app. The View is there to display your stuff and the controller is the thing that connects both.

Rails upload CSV file with header

I am using Ruby 1.9.2 with Rails 3.2.1.
I would like to create a view to upload a CSV or tab delimited file, and displays the contents of the file on the same page using a table or pagination display, then process that data in JavaScript.
How can I do this? Please walk me through any code samples you have, I am a total noob in Ruby also.
First, write a view to upload your file. You can use Paperclip for this.
Assuming you have a resource Csv, your upload form could look like this:
<%= form_for #csv, :url => csv_path, :html => { :multipart => true } do |form| %>
<%= form.file_field :attachment %>
<% end %>
Your model:
class Csv < ActiveRecord::Base
attr_accessible :attachment
has_attached_file :attachment
end
Your controller actions:
def create
#csv = Csv.create( params[:csv] )
# your save and redirect code here
end
def show
#csv = Csv.find(params[:id])
end
Having that, you can use something like this in your view:
CSV.foreach(#csv.attachment.path) do |row|
# use the row here to generate html table rows
end
Note: this is just a general idea of how this can be done and you need to have the the resource added to your routes, Paperclip gem installed and configured, etc - read the doc on how to do all that.
Just use a nice Ruby gem for parsing CSV files. This should point you in the right direction. http://fastercsv.rubyforge.org/

Reading in file contents rails

I have a form that is attempting to read in a JSON file for parsing/actions/etc. I'm having problems getting it to read in the controller.
View:
<%= form_tag({:controller => :admins, :action => :upload_json}, {:multipart => true, :method => :post}) do |f| %>
<%= file_field_tag 'datafile' %>
<%= submit_tag "Upload" %>
Controller:
def upload_json
file_data = params[:datafile]
File.read(file_data) do |file|
file.each do |line|
## does stuff here....
end
end
end
A similar function works in my seed.rb file when I'm seeding data - just can't get it to read in an uploaded file.
The error I'm getting is: can't convert ActionDispatch::Http::UploadedFile into String.
Thanks in advance for the help!
Figured it out. Needed to change:
file_data = params[:datafile]
to
file_data = params[:datafile].tempfile
And decided to use the .open function to change:
File.read(file_data) do |file|
to
File.open(file_data, 'r') do |file|
params[:datafile] is an instance of ActionDispatch::Http::UploadedFile class with tempfile attached with that.To open the tempfile
You try something like
File.open(params[:datafile].path) do |file|
#your stuff goes here
end
Open the uploaded file using path.
params[:datafile] is an instance of ActionDispatch::Http::UploadedFile class and you'll need to get at the stored file by calling path to properly process it.
Additionally, File.read will not get you the line-by-line processing you're looking for. You need to change that to File.open.
Try this:
Controller
def upload_json
uploaded_datafile = params[:datafile]
File.open( uploaded_datafile.path ) do |file|
file.each_line do |line|
# Do something with each line.
end
end
end
Alternative Style
def upload_json
File.foreach( params[:datafile].path ) do |line|
# Do something with each line.
end
# FYI: The above method block returns `nil` when everything goes okay.
end

Rails: Display an clickable image, and pass parameters

Essentially I'm dealing with 3 things: (*the first 2 work)
An Action called Index(), which accepts a user_id parameter.
A CSV export feature
A needed link to the CSV export file, from the Index view; which ideally utilizes the link_to method.
The first two work perfectly and I'm stumbling on generating a link using the link_to method because the CSV file needs to be scoped to the proper user using a user_id parameter.
Here is my Controller Code:
def index
#pro = Pro.find_by_user_id(params[:user_id])
csv_code = CSV.generate do |csv|
#pro.accounts.each do |account|
csv << [account['name'],account['number']]
end
end
respond_to do |format|
format.html
format.csv { render :csv => csv_code}
end
end
index.html.haml:
* notice I'm missing a parameter 1234, that this needs to generate a csv
= link_to image_tag "export.png", {:action => :index.csv}
The Url that works to generate a CSV:
http://localhost:3000/.csv?user_id=1234
I'm sure this is a super easy problem to solve, and I've spent an hour overlooking the obvious solution. Thank you in advance!
Just add the "(" to the image tag.
= link_to image_tag ("export.png"), {:action => :index.csv}

Get url for current page, but with a different format

Using rails 2. I want a link to the current page (whatever it is) that keeps all of the params the same but changes the format to 'csv'. (setting the format can be done by having format=csv in the params or by putting .csv at the end of the path). Eg
posts/1
=> posts/1.csv OR posts/1?format=csv
posts?name=jim
=> posts.csv?name=jim OR posts?name=jim&format=csv
I tried this as a hacky attempt
request.url+"&format=csv"
and that works fine if there are params in the current url (case 2 above) but breaks if there aren't (case 1). I could come up with more hacky stuff along these lines, eg testing if the request has params, but i'm thinking there must be a nicer way.
cheers, max
EDIT - btw, it's not guaranteed that the current page could have a named route associated with it, in case that's relevant: we could have got there via the generic "/:controller/:action/:id" route.
<%= link_to "This page in CSV", {:format => :csv } %>
<%= link_to "This page in PDF", {:format => :pdf } %>
<%= link_to "This page in JPEG", {:format => :jpeg } %>
EDIT
Add helper
def current_url(new_params)
url_for :params => params.merge(new_params)
end
then use this
<%= link_to "This page in CSV", current_url(:format => :csv ) %>
EDIT 2
Or improve your hack:
def current_url(new_params)
params.merge!(new_params)
string = params.map{ |k,v| "#{k}=#{v}" }.join("&")
request.uri.split("?")[0] + "?" + string
end
EDIT
IMPORTANT! #floor - your approach above has a serious problem - it directly modifies params, so if you've got anything after a call to this method which uses params (such as will_paginate links for example) then that will get the modified version which you used to build your link. I changed it to call .dup on params and then modify the duplicated object rather than modifying params directly. – #Max Williams
You can use:
link_to "text of link", your_controller_path(format:'csv',params: request.query_parameters)
#floor's answer was great, I found it very useful.
Although the method can be improved by using the to_params method rather than contructing your own, like so:
def current_url(new_params)
params.merge!(new_params)
"#{request.uri}#{params.to_params}"
end

Resources