What happend after send_file in Rails controller? - ruby-on-rails

In the index view, there is a link to download file:
<%= link_to filename, listing_download_path(:file => filename) %>
In the controller:
def download
pathname = File.join(USER_FOLDER, params[:file])
if File.file?(pathname)
send_file pathname
end
end
end
When the user click download, a file download popup is shown. What's happen after the file is downloaded? Does rails just sit there and do nothing more? If I delete the send_file line, dwonload.html.erb will be rendered. Does send_file skip view rendering?
What if I want to show soemthing like "You have downloaded ..."?

The send_file is a render itself. You could use the approach proposed in this question:
Rails: send_file never renders page or DoubleRender error
Basically your download link will send to a "success" view, from which you call the download file method automatically.

Related

Is it possible to use send_data (not send_file) via ajax request in rails?

I have a link that downloads a dynamically generated file. The way I have it functioning is the following:
I created a custom controller action on a resource (created a custom route in routes.rb)
Clicking the link (non-ajax at this point) directs to this custom Controller action
The file is generated, and the last line in the custom controller action is send_data which streams the document to the user.
Update: It was recommended to include code. Here is the code in that custom controller action:
class MyController < ApplicationController
def custom_action_sending_pdf
pdf = InitMyPdf.new(#user)
send_data pdf.render, filename: "complete_report.pdf", type: "application/pdf"
end
end
The issue is that request.referrer for the next request will now reference this custom controller action, and I do not want it to do that. This is also reflected in the url.
This specifically becomes an issue when later in my app I redirect back to request.referrer. When the request.referrer is this custom controller action: it does not redirect to an actual page but instead just re-downloads the document all over again.
Instead I try this:
I make the link to that custom action an ajax request via the remote: true option
On one hand: the request.referrer now properly seems to not be referencing that controller action. However, now the file is not downloading!
I have done some looking around:
This question does not have an answer that can be immediately applied.
This question applies to downloading a static file which does not work for me because my file is dynamically generated, so I use send_data to send the generated file to the user as opposed to send_file.
Question: Ultimately I am trying to let a user download a dynamically generated file, all while NOT changing the page (keeping the request.referrer the same). Is this possible?
Update
I am aware of this question but the issue with it is that it's accepted answer is using send_file, and since my file is dynamically generated: send_file will not work for me here.
This works. use button_to
Example:
<%= button_to(<custom_action_sending_pdf_path>, method: :get, class: 'btn btn-primary') do %>
<i class="fa fa-print" aria-hidden="true"></i></i> Print PDF
<% end %>
Let that button go to that custom action which downloads the pdf (same code as in original question):
class MyController < ApplicationController
def custom_action_sending_pdf
pdf = InitMyPdf.new(#user)
send_data pdf.render, filename: "complete_report.pdf", type: "application/pdf"
end
end
What happens now is when that button is clicked:
The pdf downloads and pops up adobe (at least it does this in safari)
The url of the page does not change to the custom action which prints the pdf. So for the next request: request.referrer will not be that custom action for the pdf, which is the desired behavior here.

How to render pdf into web browser in RoR

I have files in server for whom i want to keep the url confidential. For this, i created a controller that fetch the data and ultimately render it to the web broswer.
In the view
<%= link_to "Click to view the file", file_proxy( user.pdf_file_url ) %>
In users_heper.rb
def file_proxy(url)
file_proxy_path(url: url)
end
In the routes.rb
get "file_proxy" => "file_proxy#fetch"
In the controller
def FileProxy < ApplicationController
def fetch
response = HTTParty.get params[:url]
render response
end
end
I'm getting an <HTTParty::Response:0x10cd6e6a8 parsed_response="%PDF-1.3......" is not an ActiveModel-compatible object. It must implement :to_partial_path.
Do you know how to tweak this code so that it can display the PDF file correctly ?
Thanks!
You can't call render that way. It's expecting very specific options. In this case it probably looks like:
pdf_content = HTTParty.get(params[:url])
send_data(pdf_content, disposition: 'inline', type: 'application/pdf')
As a note, you probably want to limit what sorts of things that tool fetches or someone will eventually abuse it.

Allowing client to download files from ftp rails

I'm developping a web application in rails 4 and I'm currenty faced with a tiny issue.
I want to make the users of the website to be able to download files from a ftp by clicking on a link. I've decided to go on this:
def download
#item=Item.find(params[:id])
#item.dl_count += 1
#item.save
url = #item.file_url.to_s
redirect_to url and return
end
And, very basically, this in my view:
<%= link_to 'DL', controller: "items", action: "download"%>
However, I'm not quite satisfied by this, as it generates a few mistake like the fact that clicking the link create two GET methods, one responding by 403 Forbidden and the next with a 302 found...
Do you have any idea about how I could improve this?
In Rails you should do:
def download
#item=Item.find(params[:id])
#item.dl_count += 1
#item.save
url = #item.file_url.to_s
send_file url, type: 'image/jpeg', disposition: 'inline'
end
Take a look for more information http://apidock.com/rails/ActionController/DataStreaming/send_file
Note that send_file can send only from local file system.
If you need get file from remote source (should be secure location) like http://example.com/apps/uploads/tfm.zip and avoid store this file in server memory, you can first save file in #{RAILS_ROOT}/tmp/ or system /tmp and then send_file
data = open(url)
filename = "#{RAILS_ROOT}/tmp/my_temp_file"
File.open(filename, 'w') do |f|
f.write data.read
end
send_file filename, ...options...
If Rails can`t read file, you should check file permission

create a download link with rails to an external file with at URL

I moved my file storage to Rackspace Cloudfiles and it broke my send_file action.
old
def full_res_download
#asset = Asset.find(params[:id])
#file = "#{Rails.root}/public#{#asset.full_res}"
send_file #file
end
new
def full_res_download
#asset = Asset.find(params[:id])
#file = "http://86e.r54.cf1.rackcdn.com/uploads/fake/filepath.mov"
send_file #file
end
When the files were in the public file. the code worked great. When you click in the link the file would download and the webpage would not change. Now it gives this error.
Cannot read file http://86e.r54.cf1.rackcdn.com/uploads/fake/filepath.mov
What am i missing?
Thank you so much for your time.
what worked
def full_res_download
#asset = Asset.find(params[:id])
#file = open("http://86e.r54.cf1.rackcdn.com/uploads/fake/filepath.mov")
send_file( #file, :filename => File.basename(#asset.file.path.to_s))
end
real code
controler.rb
def web_video_download
#asset = Asset.find(params[:id])
#file = open(CDNURL + #asset.video_file.path.to_s)
send_file( #file, :filename => File.basename(#asset.video_file.path.to_s))
end
development.rb
CDNURL = "http://86e.r54.cf1.rackcdn.com/"
send_file opens a local file and sends it using a rack middleware. You should just redirect to the url since you're not hosting the file anymore.
As one of the comments points out, in some situations you may not be able to use a redirect, for various reasons. If this is the case, you would have to download the file and relay it back to the user after retrieving it. The effect of this is that the transfer to the user would do the following:
Request gets to your server, processing in your action begins.
Your action requests the file from the CDN, and waits until the file has been fully retrieved.
Your server can now relay the file on to the end user.
This is as compared to the case with a redirect:
Request gets to your server, processing in your action begins.
Your action redirects the user to the CDN.
In both cases the user has to wait for two full connections, but your server has saved some work. As a result, it is more efficient to use a redirect when circumstances allow.

How do I create a temp file and write to it then allow users to download it?

I'm working on my first application and I need some help with allowing my users to download a text file with certain variables that are being displayed on the page.
Take a shopping list for example.
Let's say you allow your users to create a shopping list of products, and then display the shopping list with the items on a shopping list page,
e.g. localhost:3000/list/my-list
Take a look at the example code below (which is probably incorrect):
File.open('shopping_list.txt', 'w') do |file|
file.puts 'Item 1: #{product_1.name}'
file.puts 'Item 2: #{product_2.name}'
file.puts 'Item 3: #{product_3.name}'
end
Which then creates a text file that has the following content:
Item 1: Eggs
Item 2: Butter
Item 3: Bread
Users should then be able to download this file (i don't want this file to be stored on the server) via a download link.
I have no idea how to achieve this, but I'm hoping you guys can guide me. :D
TL;DR
create text files populated with model data (perhaps create a method to achieve this?)
text files should not be stored on the server, but created as users click the download button (not sure if this is the rails way but perhaps someone could show me a better way)
I am assuming there is a resource for List with the attribute name as the name of the list and a list has_many Item which has an attribute description
First off, create a download path change your routes config/routes.rb
resources :lists do
member {get "download"}
end
Now if you run a rake routes in the console you should see a route like
/lists/:id/download
Whats more you should now have the helpers download_list_url & download_list_path to use in your view like
<ul>
<% #lists.each do |list| %>
<li> <%= list.name %> - <%= link_to 'Download List', download_list_path(list) %> </li>
<% end %>
</ul>
In your lists_controller add the action, and as you dont actually want to keep the file on the server disk just stream the data as a string
def download
list = List.find(params[:id])
send_data list.as_file,
:filename => "#{list.name}.txt",
:type => "text/plain"
end
Finally you see I have used a as_file method which you should add to the model (I prefer not to do this stuff in controllers, fat models, skinny controllers). So in the List model
def as_file
output = [self.name]
self.items.each {|item| output << item.description }
output.join("\n")
end
You say you don't want to store the file on the server, but "download" it on request; this sounds like you just want to generate and deliver a text document in response to the download link. There are several approaches, but you want to be sure of setting the mime-type so the browser sees it as a text file instead of an html document.
product_info = [
"Item 1: #{product_1.name}",
"Item 2: #{product_2.name}",
"Item 3: #{product_3.name}",
].join("\n")
render :text => product_info # implies :content_type => Mime::Type["text/plain"]
BTW, your example with open/puts above won't output what you think since single-quoted strings don't interpolate.
so, you wish to :
create text files populated with model data (perhaps create a method
to achieve this?)
text files should not be stored on the server, but
created as users click the download button (not sure if this is the
rails way but perhaps someone could show me a better way)
You have the right idea, here's what to do :
Create a method in your model to generate the text file contents. Let's say this method is called list_data
It seems like you have an existing controller action called my_list. Hence we can call our new method in the controller like so :
.
def my_list
# pre-existing code
respond_to do |format|
format.html # show html page as before
format.text do
send_data #list.list_data, :content_type => 'text/plain', :filename => 'my-shopping-list.txt'
end
end
end
To link to the download, just use link_to :action => my_list, :format => 'text'
See http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_data for full docs on send_data
Caveat & explanations : Using the method above, there isn't really an explicit creation of files, Rails is streaming it for you. Hence this method is not suitable for very large files, or when the generation of the file content will take a while. Use a delayed method to generate the file and store it - the file contents somewhere if that's the case - but we can use send_data once it has been generated
You could try a combination of TempFile and send_file. In your controller action ..
file = Tempfile.new('foo')
file.write("hello world")
file.close
send_file file.path
At Rails 2.3 you can use Template Streaming. Working with Redmine I can remember something like that, you have to adapt for your case. Reference: Streaming and file downloads
require "prawn"
class ClientsController < ApplicationController
# Generate a PDF document with information on the client and return it.
# The user will get the PDF as a file download.
def download_pdf
client = Client.find(params[:id])
send_data(generate_pdf, :filename => "#{client.name}.pdf", :type => "application/pdf")
end
private
def generate_pdf(client)
Prawn::Document.new do
text client.name, :align => :center
text "Address: #{client.address}"
text "Email: #{client.email}"
end.render
end
end
Using the Thong Kuah you must just change the "content_type" param:
def my_list
# pre-existing code
respond_to do |format|
format.html # show html page as before
format.text do
send_data #list.list_data, :content_type => 'text/plain', :filename => 'my-shopping-list.txt'
end
end
end

Resources