How to split stored data into different boxes?
How to split stored data into different boxes?

How to overcome code legacy when migrating from Paperclip to ActiveStorage?

--

Disclaimer: This is for Rails ≥ 5.2

My name is Frantisek and I am a committed engineer at Per Angusta. As many of you may encounter in your development cycle, dependencies update is not always a piece of cake! Deprecations, changelogs, … documentation is to always DRY. I would like to share with you a brief story about how I solved it.

To comply with high security policies, we differentiate our databases on a client basis and we use the same logic with uploaded files and store them in separate folders/buckets.

The wonderful paperclip gem, brought to us by thoughtbot has made the job for years, but since it is deprecated we are in the process of switching to ActiveStorage all our attachments management.

In Paperclip there was a fine feature where you can choose a specific storage path for each of the model attachable attribute and interpolate values in it as follows:

# app/models/user.rb# with Paperclip
has_attached_file :avatar,
path: ':tenant/users/:user_id/:hash/:filename'

Sadly, this path option does not exist in ActiveStorage. You are only allowed to configure two options for the has_one_attached; :dependent and the name of your attachment attribute. (EDIT: From Rails 6, you can now set the :service option to specify the storage service on a attribute basis.) The idea is to make this possible in ActiveStorage too and have something similar to the following:

# app/models/user.rb# with future ActiveStorage
has_one_attached :avatar,
key: ':tenant/users/:user_id/:hash/:filename'

First thing we though of was ‘prefix_routes’ introduced in ActiveStorage! Sadly, this was not what we were searching for! First, the prefix_routes is only available as of Rails 6 and even if we are preparing the forthcoming upgrade to Rails 6, the route prefix was not enough accurate for us. It is app specific and we wanted to make it dynamic and customizable per attribute.

That’s why we had to take action!

🐵 patching

I found in ActiveStorage source code that you can pass to the #attach method a previously created Blob. This is because ActiveStorage::Attached#attach method also accepts an existing ActiveStorage::Blob object. Hopefully the source code is enough documented!

From this point, we started to think of a monkey patching to implement those storage path by changing the ActiveStorage::Blob#key.

Here is what we came up with:

ActiveStorage monkey patching!

The Holy 🏆

I have started to think of a solution and created a PR on ActiveStorage but we were not pretty sure this feature would interest all of you!

Some questions arose:
- Is the ActiveStorage::Blob#keythe right attribute to integrate this feature?
- Is it worth to make it interpolable as in Paperclip, to avoid being requested to create an attachable_storage_path method on every model/attribute?

Here is the work in progress: ActiveStorage custom storage path. Feel free to comment bellow and on the pull request, suggestions are always welcome!

EDIT: 🎉 I finally made a Pull Request to Rails code with the following! Hopefully it will be accepted so we can migrate easily to ActiveStorage. Please 👍 if you would like to see this feature merged!

Thank you for your feedback and advices.

Interested in joining an enthusiastic engineering team?
We are recruiting talented people! Checkout our open positions here:

--

--