class DownloadsController < ApplicationController
before_action :authenticate_user!
def show
report = current_account.reports.find(params[:report_id])
authorize! :download, report
unless report.export_ready?
return head :not_found
end
url = report.signed_download_url(:export, expires_in: 2.minutes)
render json: {
url: url,
filename: report.export.filename.to_s,
expires_at: report.signed_download_expiry(expires_in: 2.minutes).iso8601
}
end
end
class ExportReadyMailer < ApplicationMailer
def ready(report)
@report = report
@recipient = report.account.owner
# Longer window since inbox links are opened well after delivery.
@download_url = report.signed_download_url(:export, expires_in: 12.hours)
return if @download_url.nil?
mail(
to: @recipient.email,
subject: "Your export \"#{@report.title}\" is ready"
)
end
end
module SignedDownloads
extend ActiveSupport::Concern
def signed_download_url(attachment_name, expires_in: 5.minutes, disposition: :attachment)
attachment = public_send(attachment_name)
return nil unless attachment.attached?
blob = attachment.blob
blob.url(
expires_in: expires_in,
disposition: disposition,
filename: blob.filename,
content_type: blob.content_type
)
end
def signed_download_expiry(expires_in: 5.minutes)
expires_in.from_now
end
end
class Report < ApplicationRecord
include SignedDownloads
belongs_to :account
has_one_attached :export
validates :title, presence: true
def export_ready?
export.attached? && processed_at.present?
end
end
Active Storage attachments are usually served through Rails' proxy or redirect controllers, but many applications need a short-lived, tamper-proof link that can be handed to a browser, an email, or a third-party service without exposing the underlying storage credentials. This snippet shows how to mint such links using the disk service's built-in signed IDs and, for cloud backends like S3, the service's native presigned URL support.
In SignedDownloads concern, the logic is packaged as a model concern so any record with an attachment can expose a consistent API. The signed_download_url method delegates to url on the blob, passing an expires_in window and a disposition so the browser is told to download rather than render inline. The Content-Disposition filename is derived from the blob so the saved file keeps a sensible name. Because the URL is generated by the storage service, when the backend is S3 the result is a real AWS presigned URL whose signature encodes the expiry — the link simply stops working after the window closes, with no server round-trip required to revoke it.
The DownloadsController wires this into an HTTP endpoint. It authorizes the current user against the owning record before ever touching the blob, which matters because a signed URL is a bearer token: anyone holding it can download the file until it expires. Handing out the shortest viable expires_in limits the blast radius if a link leaks. The controller responds with JSON containing the URL and its expiry, which is convenient for single-page apps that fetch the link and then trigger the download client-side.
In ExportReadyMailer, the same concern feeds an email. Email links tend to sit in inboxes for a long time, so a longer expiry (12.hours) is used deliberately, illustrating the trade-off between convenience and exposure. A subtle pitfall is regenerating URLs on every render: because each call produces a fresh signature, links embedded earlier still work independently, but callers should avoid caching a presigned URL past its own expiry. This pattern keeps storage private-by-default while granting narrow, time-boxed access exactly where it is needed.
Related snips
require 'application_system_test_case'
class TasksTest < ApplicationSystemTestCase
test 'deleting a task removes it from the list' do
task = tasks(:one)
System test: asserting Turbo Stream responses
class Post < ApplicationRecord
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
scope :draft, -> { where(published_at: nil) }
ActiveRecord scopes for reusable query logic
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
Share this code
Here's the card — post it anywhere.