Sometimes, I need to load a backup of the production database on my local environment.
All production blobs are stored using ActiveStorage on S3, so I have two options when doing this: either use the ActiveStorage production configuration and risk touching the production S3 bucket, or use a local DiskService but all the blobs existing in database will trigger an error.
To solve this issue, I created a new ActiveStorage::Service with a semantic similar to OverlayFS. You can configure an upper_layer where all write operations will be done, and a list of lower layers where all read operations will be delegated to (after first checking for the upper layer).
Here is the first version of the code for this service and I would very much appreciate feedbacks on the idea or the code.
class ActiveStorage::Service::OverlayService < ActiveStorage::Service
attr_reader :upper_layer, :lower_layers
def self.build(upper_layer:, lower_layers:, configurator:, **options) #:nodoc:
new \
upper_layer: configurator.build(upper_layer),
lower_layers: lower_layers.map { |name| configurator.build name }
end
def initialize(upper_layer:, lower_layers:)
@upper_layer, @lower_layers = upper_layer, lower_layers
end
delegate :upload, :update_metadata, :delete, :delete_prefixed, :url_for_direct_upload, :headers_for_direct_upload, to: :upper_layer
def exist?(key)
first_service_for(key) ? true : false
end
def download(key, &block)
send_to_first_service :download, key, &block
end
def download_chunk(key, range)
send_to_first_service :download_chunk, key, range
end
def url(key, **options)
send_to_first_service :url, key, **options
end
def path_for(key)
send_to_first_service :path_for, key
end
private
def each_service(&block)
[ upper_layer, *lower_layers ].each(&block)
end
def first_service_for(key)
each_service do |service|
return service if service.exist? key
end
nil
end
def send_to_first_service(method, key, *args, &block)
service = first_service_for(key)
service.public_send method, key, *args, &block if service
end
end
It is configured in storage.yml in the same way as a Mirror service:
local_over_amazon:
service: Overlay
upper_layer: local
lower_layers:
- amazon