I'm trying to make a simple link that when clicked initiates the download of a document associated with a particular resource. I have a Resource model and in that model there is a column called "document". I am able to successfully view the document inline when the link is clicked, but I would prefer to download it. I have been reading about content-types and send_file, but I haven't been able to completely piece this together to function.
Here is the code that I think I need to use for the link:<%= link_to 'Download File', :action => "download_file" %>
This throws an error:
ActiveRecord::RecordNotFound in ResourcesController#show
Couldn't find Resource with id=download_file
When I change the link to this, it opens the file in the browser:<%= link_to 'Open file', resource.document_url, :target => "_blank" %>
In my ResourcesController I have this method defined:
def download_file
@resource = Resource.find(params[:id])
send_file(@resource.file.path,
:filename => @resource.file.name,
:type => @resource.file.content_type,
:disposition => 'attachment',
:url_based_filename => true)
end
I have a route set up in routes.rb like this:
resources :resources do
get 'resources', :on => :collection
end
So based upon this error, it would seem that my download_file method in ResourcesController is not able to determine the id of the resource record that the document is associated with.
I'm running: Rails 3.2.11 Carrierwave 0.8.0 Ruby 1.9.3-p194
I would appreciate some insight into this. I have searched a number of articles and was not able to find a simple tutorial. Thanks.
I was able to figure this out. I will attempt to explain how I fixed this problem.
The download_file method was correctly located in the ResourcesController, but I was not using the correct variable names. Here is the correct method definition for my purposes:
def download_file
@resource = Resource.find(params[:id])
send_file(@resource.document.path,
:type => 'application/pdf',
:disposition => 'attachment',
:url_based_filename => true)
end
In my case I have a record returned in the variable @resource that has a document associated with it (again, I am using Carrierwave). So the first argument that send_file requires is the path (see the API for send_file). This path information is supplied by @resource.document.path.
send_file takes a number of other arguments, that appear to be optional because I only needed three of them. See the API documentation for the other arguments.
One of the ones that was throwing the error for me was the type:. I was passing it the variable "@resource.file.content_type", but it was apparently looking for a literal. Once I passed it application/pdf, it worked. Without explicitly setting this, the file that gets downloaded has not file extension added. In any case, for my app this document could be a variety of different mime-types from pdf to word to mp4. So I'm not sure how to specify multiple.
The really important argument for my purposes (to download instead of display in the browser) is the :disposition which needs to be set to 'attachment' as opposed to 'inline'.
The other argument :url_based_filename is apparently used to determine the name of the file from the URL. Since I'm not supplying the filename, perhaps this is the only way for me to provide that to the file being downloaded, but I'm not certain of this.
I also needed to change the link_to tag to this:<%= link_to 'Download File', :action => 'download_file', :id => resource.id %>
Note that supplying the :id symbol with the id of the current resource provided the specific information needed to identify the resource in question.
I hope this helps someone else out. This must be very obvious to everyone else, because I did not find any basic tutorials on file downloading files. I'm still learning a great deal.
UPDATE: I had to play around with this to get the desired behavior. Here is the new method that is working:
def download_file
@resource = Resource.find(params[:id])
send_file(@resource.document.path,
:disposition => 'attachment',
:url_based_filename => false)
end
With these changes I no longer have to set the :type. Setting ":url_based_filename => true" kept naming the file after the record id in the URL. Setting this to false seems counterintuitive based upon my reading of the function of this argument, however, doing so yielded the desired results of naming the file after the actual file name.
I also used the mime-types gem and add the following to my video_uploader.rb file:
require 'carrierwave/processing/mime_types'
class VideoUploader < CarrierWave::Uploader::Base
include CarrierWave::MimeTypes
process :set_content_type
This gem apparently sets the mime-type guessed from the file extension in the filename. Doing this made it unnecessary for me to have to set :type explicitly in the send_file arguments inside my download_file method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With