基于子域名的多租户应用中如何作用域化Active Storage路由?
Great question—you’re spot-on that Active Storage’s default routing doesn’t play nicely with Apartment’s schema-based multi-tenancy out of the box, but there’s a clean, maintainable way to scope those routes and fix the URL generation issue, ditching that temporary workaround for good. Let’s walk through the steps:
1. Wrap Active Storage Routes in Your Tenant Scope
First, adjust your config/routes.rb to nest Active Storage’s core routes under your :tenant_id scope. This ensures all blob/attachment requests follow your tenant-specific URL structure:
Rails.application.routes.draw do # Nest all tenant-specific routes (including Active Storage) under :tenant_id scope "/:tenant_id" do # Map Active Storage's full set of routes explicitly get "rails/active_storage/blobs/:signed_id/*filename", to: "active_storage/blobs#show", as: :rails_blob get "rails/active_storage/representations/:signed_blob_id/:variation_key/*filename", to: "active_storage/representations#show", as: :rails_blob_representation get "rails/active_storage/disk/:encoded_key/*filename", to: "active_storage/disk#show", as: :rails_disk_service put "rails/active_storage/disk/:encoded_token", to: "active_storage/disk#update", as: :update_rails_disk_service post "rails/active_storage/direct_uploads", to: "active_storage/direct_uploads#create", as: :rails_direct_uploads # Your existing tenant-scoped application routes go here resources :products, :invoices end end
This makes file URLs follow your desired subdomain.domain.com/:tenant_id/rails/active_storage/... pattern, but we still need to ensure Active Storage generates URLs with the tenant ID included.
2. Override Active Storage's URL Generation Logic
Active Storage doesn’t know about your tenant ID by default, so we’ll override the url method in ActiveStorage::Blob to inject the current tenant from Apartment. Create an initializer at config/initializers/active_storage_tenant_scoping.rb:
Rails.application.config.to_prepare do ActiveStorage::Blob.class_eval do def url(**options) # Fetch the current tenant from Apartment (adjust if you use a custom tenant resolver) current_tenant = Apartment::Tenant.current if current_tenant.present? # Use the scoped route helper with the tenant ID parameter rails_blob_path(self, options.merge(tenant_id: current_tenant)) else # Fall back to default behavior if no tenant is set (e.g., admin contexts) super end end # Optional: Add tenant ID to blob keys for cloud storage separation def generate_unique_secure_token tenant_prefix = Apartment::Tenant.current.presence || "default" "#{tenant_prefix}/#{SecureRandom.base58(24)}" end end end
- The
urloverride ensures every generated blob URL includes the active tenant ID, matching your application’s routing structure. - The optional
generate_unique_secure_tokentweak adds a tenant prefix to your blob keys—critical if you’re using cloud storage like S3, as it physically separates files from different tenants in your bucket to avoid collisions.
3. Handle Cloud Storage (If Applicable)
If you’re using a cloud storage service (S3, Google Cloud Storage, etc.), update your config/storage.yml to work with the tenant-specific blob keys we added. You don’t need to hardcode a prefix here—our initializer already adds the tenant ID to the blob key:
amazon: service: S3 access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> bucket: <%= ENV['AWS_BUCKET'] %>
Why This Beats the Temporary Default URL Workaround
Your current default URL fix works for basic cases, but it’s fragile: it might fail in background jobs, admin contexts, or if your tenant resolution logic changes. The approach above ties directly into Apartment’s tenant state and Rails’ routing system, making it more maintainable and robust long-term.
内容的提问来源于stack exchange,提问作者coding addicted

