Rails download file from show action? - ruby-on-rails

I have an uploader which allows you to upload documents. What I want to do is trigger a download for the document when you view its show action. The url would be something like:
/documents/16
This document could be .txt, or .doc.
So far, my show action looks like this:
def show
#document = Document.find(params[:id])
respond_with(#document) do |format|
format.html do
render layout: false, text: #document.name
end
end
end
How would I go about achieving this?

Take a look at the send_data method:
Sends the given binary data to the browser. This method is similar to render :text => data, but also allows you to specify whether the browser should display the response as a file attachment (i.e. in a download dialog) or as inline data. You may also set the content type, the apparent file name, and other things.
So, I think in your case it should be something like this:
def show
#document = Document.find(params[:id])
send_data #document.file.read, filename: #document.name
end

I created a new method in my controller for downloading a file. It looks like this. Stored_File is the name of the archived file and has a field called stored_file which is the name of the file. Using Carrierwave, if a user has the access/permissions to download the file, the URL will display and then send the file to the user using send_file.
Controller
def download
head(:not_found) and return if (stored_file = StoredFile.find_by_id(params[:id])).nil?
case SEND_FILE_METHOD
when :apache then send_file_options[:x_sendfile] = true
when :nginx then head(:x_accel_redirect => path.gsub(Rails.root, ''), :content_type => send_file_options[:type]) and return
end
path = "/#{stored_file.stored_file}"
send_file path, :x_sendfile=>true
end
View
<%= link_to "Download", File.basename(f.stored_file.url) %>
Routes
match ":id/:basename.:extension.download", :controller => "stored_files", :action => "download", :conditions => { :method => :get }

Related

How to download file with id params?

Hello I try to download a jpeg image on rails by verifying that the Post exists and by recovering its id in parameter.
I try something but i got a error... I show you:
def download
send_file '/public/uploads/posts/#image/image.jpg', :type => 'image/jpeg', :disposition => 'attachment', :x_sendfile => true
end
private
def set_image
#image = Post.find(params[:id])
end
In my controller I have to in my routes get download.
And my link_to is:
<%= link_to "Download", download_posts_path %>
But rails say to me "Couldn't find Post without an ID".
I don't understand why... He dont't have the id but I dont't know why ?
Assuming you use set_image in a before_action filter.
First you should pass the post instance or id to your route helper :
If your route takes a param e.g. /posts/:id/download :
<%= link_to "Download", download_posts_path(#post) %>
<%= link_to "Download", download_posts_path(#post.id) %>
If not you can pass it with a query parameter e.g. posts/download/?id=1
<%= link_to "Download", download_posts_path(id: #post.id) %>
Both solution will provide you a params[:id] in your controller. Otherwise params[:id] will be nil and find raises an error.
Then there's something wrong in the download action as #DileepNandanam pointed out. You're not using your Post instance (#image) at all, you're just passing send_file a string containing "#image", not the variable but just a string. You may want to use interpolation to build a valid path to your image. For example if your #image has a :name which could be "image.jpg" you would do it like this:
send_file "/public/uploads/posts/#{#image.name}"
Or you could name your images with the post id like 13.jpg then you'll do :
send_file "/public/uploads/posts/#{#image.id}.jpg"
Or even create separate forlders with post's ids :
send_file "/public/uploads/posts/#{#image.id}/image.jpg"
send_file "/public/uploads/posts/#{#image.image_file_name}/image.jpg", :type => 'image/jpeg', :disposition => 'attachment', :x_sendfile => true
The method image_file_name may varies depends on the attachment you have specified on model
A better way is to use the url for attachment like
send_file #image.image.url(:original)

Rails Controller Model Object Passing

This is more of an architecture/functional question for Rails. I have a search function which sends the criteria to the model where the query resides. The search works. Now I have a CSV export link <%= link_to "CSV", contacts_path(format: "csv") %> in my view file which points to localhost/books.csv.
The export didn't work without my search parameters (so localhost/book.csv?book_name=foo works as expected). What I do in send_data is I pass the #books object to the .to_csv function inside my model, and it becomes nil without passing the parameter also. Pls see code below.
My controller:
def index
#books = Book.search(params[:search])
respond_to do |format|
format.html
format.csv { send_data Book.to_csv(#books) }
end
My model:
def self.search(criteria)
find(:all, :conditions => ['book_name LIKE ?', "%#{criteria}%"])
end
def self.to_csv(search_results)
CSV.generate do |csv|
csv << column_names
search_results.each do |contact|
csv << contact.attributes.values_at(*column_names)
end
end
end
I like to understand why. The current setup seems to be making another request to the server in order to generate the CSV file, and that's why it requires the parameters in localhost/books.csv request. Is this correct?
Now, if instead I put the query inside the controller like below, the CSV request works as expected (so I just click the link and receive the file).
def index
#books = Book.find(:all, :conditions => ['book_name LIKE ?', "%#{criteria}%"]) respond_to do |format|
format.html
format.csv { send_data Book.to_csv(#books) }
end
I love to keep the query inside the model for the sake of organization, so would be awesome if you guys can point me to the right direction. Thanks!
I would suggest that you change your link to something like this:
<%= link_to "CSV", contacts_path(params.merge(format: "csv")) %>
This will pass down the current search parameters plus the new option for the format to be CSV. Then you can continue to keep the search method inside the model in the way that you had originally written it.

How do I add different types of GET routes that require parameters in Ruby on Rails

I have a list of users being displayed, you can click on "Show user" or "PDF" to see details of that user in HTML or as a PDF document. The show was automatically created with scaffolding, now I'm trying to add the option to view it as a PDF. The problem is adding a second GET option, if I pass the user along as a parameter, it is assumed to be a POST and I get an error that the POST route does not exist. I am not trying to update the user, just to show it in a different way, basically to add a second "show user" option.
How do I tell it that I want a GET, not a POST? Is there an easier way to do what I am trying to do? Thanks.
Please, create a controller like this:
class ClientsController < ApplicationController
# The user can request to receive this resource as HTML or PDF.
def show
#client = Client.find(params[:id])
respond_to do |format|
format.html
format.pdf { render pdf: generate_pdf(#client) }
end
end
end
Please, update route.rb file, action name with post and get, like below :
match 'action_name', to: 'controller#action', via: 'post'
match 'action_name', to: 'controller#action', via: 'get'
More info please read this link : "http://guides.rubyonrails.org/routing.html"
you haven't posted any code or details, so I am guessing you want something like this:
routes
resources :users
controller
class UsersController < ActionController::Base
def show
#user = User.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.pdf # handle the pdf response
end
end
end
view file in views/users/show.pdf.prawn
prawn_document() do |pdf|
#user.each {|r| pdf.text r.id} # will print user id of the user
end
The way above example will work is, if something visits the following URLs, they will get html file:
localhost:3000/users/1 #html is the default format in rails
localhost:3000/users/1.html
but if they visit .pdf, they will be served a pdf format.
localhost:3000/users/1.pdf
If the above assumptions are correct, then check prawn or wicked_pdf pdf gem. the above example uses prawn
Checkout this link http://apidock.com/rails/ActionController/MimeResponds/InstanceMethods/respond_to. You can add a new MIME type and pass on the :format as pdf in all your rails routes.
Hope this will help.
And for the POST-request check your
config/routes.rb
There shoud be a few routes already, so you can infer the route you need.
In your link you can pass an additional parameter called format for pdf. For e.g.
<%= link_to 'Display in PDF', "/user/pdf", :format => "pdf" %>

Ruby on rails, forcing the user to download a tmp file

I've created a file in the tmp directory with the following controller code:
def download
file_path = "#{RAILS_ROOT}/tmp/downloads/xxx.html"
data = render_to_string( :action => :show, :layout => nil )
File.open(file_path, "w"){|f| f << data }
flash[:notice] = "saved to #{file_path}"
end
This creates the file I wanted in the tmp directory, what I want to do is force the user to download that file.
On my local machine, the file is saved to path like:
/Users/xxxx/Documents/Sites/xxxx/Website/htdocs/tmp/downloads/xxxx.html
And on the live server this url will be somthing totally different.
What I was wondering is how do I force the user to download this xxxx.html ?
P.S.
If I put a...
redirect_to file_path
...on the controller it just give's me a route not found.
Cheers.
Take a look at the send_file method. It'd look something like this:
send_file Rails.root.join('tmp', 'downloads', 'xxxxx.html'), :type => 'text/html', :disposition => 'attachment'
:disposition => 'attachment' will force the browser to download the file instead of rendering it. Set it to 'inline' if you want it to load in the browser. If nginx is in front of your Rails app then you will have to modify your environment config (ie. environments/production.rb):
# For nginx:
config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
It's easy to confuse file paths with URLs, but it is an important distinction. What has a URL path of /a/b.txt is actually located in the system path #{Rails.root}/public/a/b.txt so you may need to address this by generating both in tandem.
Here's how you might address that:
def download
base_path = "downloads/xxx.html"
system_path = File.expand_path("public/#{base_path}", Rails.root)
url_path = "/#{base_path}"
File.open(file_path, "w") do |f|
f.puts render_to_string(:action => :show, :layout => nil)
end
flash[:notice] = "saved to #{base_path}"
redirect_to(url_path)
end
You cannot redirect to a resource that is not exposed through your web server, and generally only things in public/ are set this way. You can include additional paths if you configure your server accordingly.
You can also side-step this whole process by simply rendering the response as a downloadable inline attachment, if you prefer:
render(:action => :show, :layout => nil, :content_type=> 'application/octet-stream')

Rails; save a rendered views html content to file

I'm trying to create a view with a download link to download the html source?
#Peter 's solution worked for me. Here is a code sample:
View:
<%= link_to 'download this page', object_path(#object, :download => true) %>
Controller:
def show
# ...
if params[:download]
send_data(render_to_string, :filename => "object.html", :type => "text/html")
else
# render normally
end
end
You can use render_to_string instead of render, which will give you the page, then to download it use send_data.
More on render to string here, and more on send_data here.

Resources