问题
I'm making a webapp with paid downloads, and I followed Carrierwave's guide for protecting uploads. Everything is working as expecting save one thing. Here's my current setup:
Product File Uploader
...
def store_dir
"#{Rails.root}/downloads/#{model.product_id}/#{model.id}"
end
...
Routes
get "/downloads/:product_id/:product_file_id/:filename", :controller => "products", action: "download", conditions: {method: :get}
ProductsController
def download
product_file = ProductFile.find(params[:product_file_id])
name = File.basename(product_file.file.file.file)
send_file "#{Rails.root}/downloads/#{product_file.product.id}/#{product_file.id}/#{name}", :x_sendfile => true
end
The problem is with the download path. Because I specified #{Rails.root}
in the File Uploader, the file saves in a folder external from the public folder called downloads
. This has an unintended side effect, however. When I retrieve the URL from my model, I get the entire absolute path, not the path relative to the Rails Root.
So for instance, ProductFile.first.file.url
returns "/home/doomy/Documents/resamplr/downloads/3/1/aaaa.zip"
, when it should instead just return /downloads/3/1/aaa.zip
.
This behaviour seems to be attached to what is specified in the ProductFileUploader
.
For instance, if I change the store_dir
to "/downloads/#{model.product_id}/#{model.id}"
, I get an access error as it is trying to upload to the root of my PC. "downloads/#{model.product_id}/#{model.id}"
simply uploads to a new folder in the public
directory called downloads, which is not what I want to do.
Any advice on how to proceed? Thanks.
Edit:
I have found that setting the config.root
in a Carrierwave config file to the Rails.root
allows for this behavior. If I do this, in my ProductFileUploader
I can specify the store_dir
as "downloads/#{model.product_id}/#{model.id}
.
However, this means public files are saved with a "/public/..." link. Rails automatically discards the public foldername and uses subdirectory names in the URL.
回答1:
Found out how to solve this!
In each uploader, there is a special method called root
that we can use to set the unseen filepath. First, I made a special uploader base class called PublicUploader.
class PublicUploader < CarrierWave::Uploader::Base
def root
"${Rails.root}/public"
end
end
Then, I set the global CarrierWave root to the Rails.root.
CarrierWave.configure do |config|
# These permissions will make dir and files available only to the user running
# the servers
config.permissions = 0600
config.directory_permissions = 0700
config.storage = :file
# This avoids uploaded files from saving to public/ and so
# they will not be available for public (non-authenticated) downloading
config.root = Rails.root
end
This means in my private file uploader I can simply write
def store_dir
"downloads/#{model.product_id}/#{model.id}"
end
and it will save in "#{Rails.root}/downloads/..."
as opposed to "#{Rails.root}/public/downloads/..."
. This means I can use a controller to limit access.
However, in my public uploader, I simply had...
def store_dir
"public/downloads/#{model.product_id}/#{model.id}"
end
...because I wanted to save in my "#{Rails.root}/public"
folder. However, this meant that rails would show a url like the following
/public/downloads/myfile.jpg
This poses a problem, as Rails omits the public
from the path. So the above would 404. However when the uploader inherits from the special class where I define the root
, then I can simply say
def store_dir
"downloads/#{model.product_id}/#{model.id}"
end
It will upload and show the image correctly! Hope this can help someone.
来源:https://stackoverflow.com/questions/41337412/show-rails-carrierwave-url-without-exposing-entire-path