I have ActiveStorage installed and set up. There is a model invoice
which can have a PDF file attached:
class Invoice < ApplicationRecord
has_one_attached :generated_pdf
Attaching PDFs works fine and I can download them with their public URL. Now because the data is invoice data I would like to turn off public URLs and serve the file via redirect from inside a controller. This way I can add authorization for requested files.
I have turned off public URLs following the guide's section 5.3.
config.active_storage.draw_routes = false
For the sake of getting this to work, for now I try to redirect to the attachment in the invoice controller show method.
class InvoicesController < ApplicationController
def show
redirect_to @invoice.generated_pdf.url
end
end
However, I get the error message Cannot generate URL for file.pdf using Disk service, please set ActiveStorage::Current.url_options.
A bit of research and it seems adding the following to the controller could fix this:
class InvoicesController < ApplicationController
include ActiveStorage::SetCurrent
Now I get a different error message undefined method rails_disk_service_url for module #<Module:0x0000000124de6550>
I have read and re-read section 5.3 but I can't wrap my head around what I have to do here.
When you try to call .url
on an ActiveStorage attachment with Disk
service, it raises because Disk storage doesn't support public URLs — it's meant to serve via Rails routes
In any case if you decide to hide public attachment URL, it's better to stream attachment instead
To make it reusable, define method in the parent controller (i.e. ApplicationController
in your case) or using concerns
include ActionController::Live
def stream_active_storage_attachment(attachment)
send_stream(filename: attachment.filename.to_s, type: attachment.content_type) do |stream|
attachment.download { |chunk| stream.write(chunk) }
end
end
and call it inside your controller after authorization
def show
# some authorization to prevent download by users without priveliges
stream_active_storage_attachment(@invoice.generated_pdf)
end
In this case you will not show any URLs outside the backend
Another option is to stream using web server (e.g. nginx). In this case you will need to to use X-Accel-Redirect
header. It's better for very huge files