Scroll down for my blog.
I'm a Ruby developer with a strong devops background and leadership experience. I have a personal interest in languages like Crystal and Rust. I like building things.
ActiveStorage makes it really easy to upload files from Rails to an S3 bucket or an S3-compatible service, like DigitalOcean Spaces. Refer to the official documentation if you’d like to know more about setting up ActiveStorage.
If your uploads are meant to be public and you were thinking of serving them directly through the CDN sitting in front of your S3 bucket, you’ll soon notice a problem: ActiveStorage URLs are built to always go through your Rails app, mainly through ActiveStorage::BlobsController. This controller is responsible for setting the cache headers and redirecting to the bucket URL. Your Rails app will be the first point of contact even if it’s just to retrieve the bucket URL. On top of that, there’s no place to specify a CDN host to replace the bucket host.
Fortunately, there is an easy way to go around this problem. In order to translate stored files into URLs, Rails provides the URL helper rails_blob_url, which basically resolves to this
ActiveStorage::BlobsController. We’d like to introduce a new helper that points directly to our CDN host.
Though there are different ways of solving this problem, I found using Rails direct routes an elegant solution. Rails direct routes provide a way to create URL helpers directly from your
# config/routes.rb direct :rails_public_blob do |blob| File.join("https://cdn.example.com", blob.key) end
You can call this route the same way you’d call the original Rails URL helper:
class User has_one_attached :profile_picture end rails_public_blob_url(User.first.profile_picture) # => https://cdn.example.com/j8rte71tp8xpq5afr3uqxlcqtkzn # You can also use this outside views Rails.application.routes.url_helpers.rails_public_blob_url(User.first.profile_picture)
Finally, let’s refactor our route a bit:
# config/routes.rb direct :rails_public_blob do |blob| # Preserve the behaviour of `rails_blob_url` inside these environments # where S3 or the CDN might not be configured if Rails.env.development? || Rails.env.test? route_for(:rails_blob, blob) else # Use an environment variable instead of hard-coding the CDN host File.join(ENV.fetch("CDN_HOST"), blob.key) end end
You can use this new URL helper whenever your ActiveStorage files are to be served directly through a CDN.
To conclude, Rails 6.1 will allow defining multiple storage services for the same environment, which means you’ll be able to use both public and private buckets from your code. This makes using public buckets and CDNs an even more viable option than before. See this PR for more details.