Search code examples
ruby-on-railsrubyrails-activestorage

Graceful fallback for non-default Active Storage service in Rail 6.1


I recently took advantage of Rails 6.1's support for using multiple different Active Storage services in one app, on an association-by-association basis:

class MyClass < ApplicationRecord
  has_many_attached :cool_documents
  has_many_attached :even_cooler_documents, service: :my_new_service
end

with a storage.yml that looks something like this:

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

my_first_service:
  service: S3
  access_key_id: <%= ENV['AWS_ACCESS_KEY'] %>
  secret_access_key: <%= ENV['AWS_SECRET_KEY'] %>
  region: us-east-1
  bucket: <%= ENV['MY_FIRST_BUCKET'] %>

my_new_service:
  service: S3
  access_key_id: <%= ENV['AWS_ACCESS_KEY'] %>
  secret_access_key: <%= ENV['AWS_SECRET_KEY'] %>
  region: us-east-1
  bucket: <%= ENV['MY_NEW_BUCKET'] %>

However, we have a developer quality-of-life problem -- we really like having a fallback mechanism for local dev that says "Do you have the ENV variables needed for the AWS storage config? If so, let's use it. If not, let's use local storage." This manifested in some code in our development.rb config file like this:

  config.active_storage.service = if ENV["AWS_ACCESS_KEY"].present? && ENV["AWS_SECRET_KEY"].present?
    :my_first_service
  else
    :local
  end

This worked GREAT when there was just one relevant Active Storage service for the app -- the default. However, the config for my_new_service breaks this -- you've gotta have the right ENV config present to configure my_new_service, or the app throws errors when it cannot configure the service. Furthermore, so far I cannot figure out how to make this work gracefully and to "hot swap" config the way I'd like to. Does anybody out there have any ideas on how we could configure this?


Solution

  • Your storage.yml is an erb file so you could just manage it in there. It's not great but should work.

    local:
      service: Disk
      root: <%= Rails.root.join("storage") %>
    
    my_first_service:
      service: S3
      access_key_id: <%= ENV['AWS_ACCESS_KEY'] %>
      secret_access_key: <%= ENV['AWS_SECRET_KEY'] %>
      region: us-east-1
      bucket: <%= ENV['MY_FIRST_BUCKET'] %>
    
    my_new_service_prod:
      service: S3
      access_key_id: <%= ENV['AWS_ACCESS_KEY'] %>
      secret_access_key: <%= ENV['AWS_SECRET_KEY'] %>
      region: us-east-1
      bucket: <%= ENV['MY_NEW_BUCKET'] %>
    
    <% if Rails.env.production? %>
    my_new_service:
      service: my_new_service_prod
    <% else %>
    my_new_service:
      service: local
    <% end %>
    

    In production my_new_service will now point to local, otherwise to local.