At my work we are have a use case for ActiveStorage to be backed by multiple attachment / blob tables across separate DBs. Our specific use case is that we are about to move to deploying our Rails application regionally to respect data sovereignty requirements. Our plan is to utilise Rails multi-DB support to point models that store user generated data to regional DBs, while pointing all other models to a single global DB. Since we have both global and regional models with attachments, we need ActiveStorage tables in both databases if we are to respect customer data sovereignty and not have missing assets across regions.
If you want Active Storage to look in different DBs for attachments to different records, that’s probably going to require framework support.
So here I am, requesting the feature! We only need this at a model level though, not record. I can imagine this working something like this:
# My custom AS models
class Global::Blob < ActiveStorage::Blob
connects_to(database: { writing: :global, reading: :global })
end
class Global::VariantRecord < ActiveStorage::VariantRecord
connects_to(database: { writing: :global, reading: :global })
end
class Global::Attachment < ActiveStorage::Attachment
blob_class Global::Blob
variant_class Global::VariantRecord
connects_to(database: { writing: :global, reading: :global })
end
# I am a regular "regional" model
class User < ApplicationRecord
# just uses ActiveStorage::Attachment, which uses the default regional DB
has_one_attached :picture
end
# I am a global model
class BlogPost < ApplicationRecord
connects_to :global
# This is how I explicitly override a class to use a different
has_one_attached :picture, class: Global::Attachment
end
But I have no strong opinions there, just looking to see if this is a feature Rails would consider adopting? We are happy to contribute to the implementation. It would be my/our first Rails contribution, so guidance on how to approach it would be very much appreciated.
I’m facing the same issue discussed in this topic, where our system architecture involves several databases across distributed systems, and we’ve recently implemented a sort of data warehouse that captures read replicas. Each microservice in our infrastructure had its own blob and attachment tables, and now we’re facing challenges in retrieving data due to the need to switch connections across different databases. My proposal to address this challenge is to enhance the has_one_attached method in Active Storage to accept options that specify which database to use, much like:
has_one_attached :image, db: :my_custom_db
This modification would greatly streamline our data access in a distributed system setup by allowing explicit database selection at the model level. It aligns with our goal of centralizing data access while respecting the distributed nature of our microservices architecture and the specific data sovereignty requirements we have. Adopting such a feature would be a significant step forward in making Active Storage more adaptable to complex, distributed Rails applications.
Hi, I’ve looked into this issue and tried to implement multi-db support into Active Storage but I think ultimately the implementation will need to be rewritten to not use an engine and instead use meta-programming to dynamically generate the models and controllers for the attachments/blobs/etc.
The reason is that while I could change the connection and pass that down to the has_one_attached association, there is no reasonable way I could find to pass the correct model down to the controller. If your model is Global::Blob, Active Storage only knows about ActiveStorage::Blob in the controller (see rails/activestorage/app/controllers/active_storage/direct_uploads_controller.rb at main · rails/rails · GitHub). And so how can ASt figure out the right model here? I wasn’t able to come up with an answer. I don’t think we can half implement this on just the model level, it wouldn’t work well for sharded applications.
I would gladly accept a PR that fixes this, but it’s a lot more complex than proposed here.
For us, just the model changes are sufficient. Since we aren’t doing direct uploads, and are instead setting the attachment directly on the engine model.
Could you share the changes for passing the connection down to the has_one_attached? Because right now, what we’re doing is basically copying the gem wholesale, and namespacing the ActiveStorage models, and then updating all the references to use the namespace. Such a hack to maintain and upgrade.
It allows us to pass the attachment and blob models to has_one_attached.
This still requires that we copy the correct version of the models and overwrite them, so that they can inherit from our engine’s ApplicationRecord. Because of how connections are tied to classes, this was the easiest and most straight forward solution. It’s not great, but it strikes a balance. Reduces the amount of internals we need to overwrite, and allows us to continue to reference the models directly from within the engine via ActiveStorage::Blob because the namespace is already isolated.