download csv file to computer - ruby-on-rails

From ajax I call my export action in my controller
def export
data_filtered = eval(params[:data_filtered][9..-2])
CSV.open("data.csv", "wb") do |csv|
csv << data_filtered.first.keys
data_filtered.each do |hash|
csv << hash.values
end
end
end
Currently this does indeed create a file called data.csv on my computer but it doesn't show any evidence of actually doing so. I want the file to be downloaded by the browser and show up in the browser downloads and also in my downloads folder.
EDIT:
def export
data_filtered = eval(params[:data_filtered][9..-2])
csv_file = CSV.generate({}) do |csv|
csv << data_filtered.first.keys
data_filtered.each do |hash|
csv << hash.values
end
end
send_data csv_file, :type => 'text/csv; charset=iso-8859-1; header=present', :disposition => "attachment; filename=tester1.csv"
end

You need to set headers to indicate what the browser should be doing with the resulting file.
send_data file, :type => 'text/csv; charset=iso-8859-1; header=present', :disposition => "attachment;data=#{csv_file}.csv"
However, it would have to be written first, to the server, and then downloaded. You might find this answer helpful: Ruby 1.9.2 export a CSV string without generating a file

Well it appears you are not sending back any response from your controller method, which is why nothing is happening in the browser. You are just processing the CSV.
To send back a downloadable entity a response, one way would be to use either send_data or send_file, depending on what format you have the data in (binary vs. a file).
Both essentially do the same -- allow you to specify the data, the data type, and a downloadable file name that you specify. It can be returned as a response, just like render calls.
Hope that helps!

Related

Rails downloads PDF instead of displaying it

In my Rails app I'm using Faraday to connect with 3rd party API. In one of the endpoint I'm downloading the pdf which means that the API will send me a binary file as a response. Now I want to display this pdf to the user, to make it work I've got below controller action:
def document
file = client.document(params[:id])
send_data(file, disposition: 'inline', type: 'application/pdf')
end
But instead of displaying the PDF, the download is started. What did I missed?
Found the solution, maybe it will be helpful for someone in the future.
All I need was to set Content-Disposition headers like below:
def document
file = client.document(params[:id])
send_data(file, disposition: 'inline', type: 'application/pdf', headers: { 'Content-Disposition' => 'inline' })
end

Invalid/damaged download using send_data with PowerPoint

I am generating a PowerPoint using the powerpoint gem and up until now I was using send_file, but now I want to use send_data. The reason for this is that send_data blocks the application from doing anything else until the call completes, while send_file allows the app to continue executing while the file downloads, which sometimes, due to coincidental timing, leads to the file being deleted before the user tells their browser to start the download and the web server finishes sending the file and thus a blank or incomplete file is downloaded.
This is how I create my PowerPoint:
#Creating the PowerPoint
#deck = Powerpoint::Presentation.new
# Creating an introduction slide:
title = 'PowerPoint'
subtitle = "created by #{username}"
#deck.add_intro title, subtitle
blah blah logic to generate slides
# Saving the pptx file.
pptname = "#{username}_powerpoint.pptx"
#deck.save(Rails.root.join('tmp',pptname))
Now, on to what I have tried. My first instinct was to call as follows:
File.open(Rails.root.join('tmp',"#{pptname}"), 'r') do |f|
send_data f.read, :filename => filename, :type => "application/vnd.ms-powerpoint", :disposition => "attachment"
end
I've also tried sending the #deck directly:
send_data #deck, :filename => pptname, :disposition => "attachment", :type => "application/vnd.ms-powerpoint"
Reading it ends with a larger file than expected and it isn't even able to be opened and sending the #deck results in a PowerPoint with one slide that simply has the title: #Powerpoint::Presentation:0x64f0dd0 (aka the powerpoint object).
Not sure how to get the object or file into binary format that send_data is looking for.
You need to open the file as binary:
path = #deck.save(Rails.root.join('tmp',pptname))
File.open(path, 'r', binmode: true) do |f|
send_data f.read, filename: filename,
type: "application/vnd.ms-powerpoint",
disposition: "attachment"
end
This can also be done by calling the #binmode method on instances of the file class.
send_data #deck, ...
Will not work since it calls to_s on the object. Since Powerpoint::Presentation does not implement a #to_s method you're getting the default Object#to_s.

Rails send multiple files to browser using send_data or send_file

I am trying to send multiple files to the browser. I cant call send_data for every record like in the code below because i get a double render error. According to this post i need to create the files and zip them so i can send them in one request.
#records.each do |r|
ActiveRecord::Base.include_root_in_json = true
#json = r.to_json
a = ActiveSupport::MessageEncryptor.new(Rails.application.config.secret_token)
#json_encrypted = a.encrypt_and_sign(#json)
send_data #json_encrypted, :filename => "#{r.name}.json" }
end
I am creating an array of hashes with the #json_encrypted and the file_name for each record. My question is how can i create a file for every record and then bundle them into a zip file to then pass that zip file to send_file. Or better yet have multiple file download dialogs pop up on the screen. One for each file.
file_data = []
#records.each do |r|
ActiveRecord::Base.include_root_in_json = true
#json = r.to_json
a = ActiveSupport::MessageEncryptor.new(Rails.application.config.secret_token)
#json_encrypted = a.encrypt_and_sign(#json)
file_data << { json_encrypted: #json_encrypted, filename: "#{r.name}.json" }
end
So the issue i was having is that send_file does not respond to an ajax call which is how i was posting to that action. I got rid of the ajax, and am sending the necessary data though a hidden_field_tag and submitting it with jquery. The code below creates files for the data, zips them, and passes the zip file to send_data.
file_data.each do |hash|
hash.each do |key, value|
if key == :json_encrypted
#data = value
elsif key == :filename
#file_name = value
end
end
name = "#{Rails.root}/export_multiple/#{#file_name}"
File.open(name, "w+") {|f| f.write(#data)}
`zip -r export_selected "export_multiple/#{#file_name}"`
send_file "#{Rails.root}/export_selected.zip", type: "application/zip", disposition: 'attachment'
end

Rails download http response/displayed site

Instead of displaying the xml file rendered by the index.api.rsb file in my browser, i want to download it. To me this sounds very simple, but I cant find a solution.
I tried the following in the controller-method:
def split
if params[:export] == "yes"
send_file *here comes the path to xml view*, :filename => "filename", :type => :xml
end
respond_to ...
end
The result is a MissingFile exception...
Thanks in advance
Note that :disposition for send_file defaults to 'attachment', so that shouldn't be a problem.
If you have a MissingFile exception, that means the path is incorrect. send_file expects the path to an actual file, not a view that needs to be rendered.
For your case, render_to_string might be what you need. Refer to this related question. It renders the view and returns a string instead of setting the response body.
def split
if params[:export] == "yes"
send_data(render_to_string path_to_view, filename: "object.xml", type: :xml)
end
end
To force it to download it, add :disposition => attachment to your send_file method.
Source: Force a link to download an MP3 rather than play it?

CSV.open and send_data in rails....?

I may end up figuring this out later, but i thought I'd try.
Can someone help combine send_data and CSV.open
According to the docs, you can CSV.open filename, mode(whatever that is) and basically a file will save to your current path. However, if you want to send that file to a user through his broswer, as most of us who give the option of CSV files to download, do,do... then can we combine the CSV.open with send_data?
thoughts?
examples welcome if you do something like this too.
I don't think you want to combine those two things.
CSV.open will save the data to a file, which you would need to read back in in order to send it through send_data.
But you can do something like:
csv = []
csv << ["titles", "for", "csv"]
csv << ["data", "for", "csv"]
send_data(csv.collect{|s| s.join(",")}.join("\n"),
:type => 'text/csv; charset=utf-8; header=present',
:filename => "mytitle.csv")
Which should prompt the user to download the csv file.

Resources