When handling sensitive documents like invoices or contracts in a Rails app, it’s risky to expose public file URLs, even if they expire. Luckily, Rails allows you to stream attachments through controllers, letting you add authentication and authorization while showing files inline in the browser.
In this, we’ll show you how to securely preview or download PDFs from ActiveStorage without generating any public URL – using a clean, reusable approach.
In many applications, models such as Invoice often include sensitive file attachments like generated PDF documents. For example:
class Invoice < ApplicationRecord has_one_attached :generated_pdf end
To enhance security, you may disable ActiveStorage’s public routes by configuring the application:
# config/application.rb config.active_storage.draw_routes = false
If you attempt to access the file using a direct call like:
redirect_to @invoice.generated_pdf.url
It results in errors such as:
Cannot generate URL using Disk service... undefined method `rails_disk_service_url`...
This happens because ActiveStorage cannot generate public URLs when using the Disk service or when routes are disabled.
Instead of generating a signed URL, you can stream the file directly from your controller.
# app/controllers/concerns/streamable_attachment.rb
module StreamableAttachment
extend ActiveSupport::Concern
def stream_attachment(record:, attachment_name:, disposition: "inline")
attachment = record.public_send(attachment_name)
return head :not_found unless attachment.attached?
blob = attachment.blob
response.headers['Content-Type'] = blob.content_type
response.headers['Content-Disposition'] = "#{disposition}; filename=\"#{blob.filename}\""
response.headers['Content-Length'] = blob.byte_size.to_s
blob.download do |chunk|
response.stream.write chunk
end
ensure
response.stream.close
end
end
class InvoicesController < ApplicationController include StreamableAttachment def show_pdf invoice = Invoice.find(params[:id]) # authorize invoice(Use Pundit or your own logic stream_attachment(record: invoice, attachment_name: :generated_pdf, disposition: "inline") end end
# config/routes.rb resources :invoices do member do get :show_pdf end end
<% if @invoice.generated_pdf.attached? %> <% else %>No PDF attached.
<% end %>
Signed URLs are great for performance, but:
Work with our skilled Ruby on Rails developers to accelerate your project and boost its performance.
Hire Ruby on Rails Developer