Extend ActiveStorage::Blob with callbacks
During my experimenting with ActiveStorage I came to learn that while it’s been in Rails since version 5.2, it still lacks both validations and callbacks for you attached blobs. Luckily the there’s a community maintained gem with some basic validations, called active_storage_validations, the callbacks you have to add on your own.
To understand the challenge, let us first look at the flow of attaching an ActiveStorage blob to a new ActiveRecord model (let’s call it User):
- Blob gets uploaded
- Blob is attached to a in-memory instance of the new
User - If the
Userobject is valid, it, and theBlobgets persisted. - Asynchronously, the
Analyzercompletes it work, and updates theBlobwith new metadata.
I want my user to be notified after the blob gets updated with new metadata, so I can refresh the Elasticsearch index with the new information. And while the association model between my User and Blob, ActiveStorage::Attachment has a belongs_to :record, touch: true, nothing happens if a blob gets updated.
So let us utilize some of the magic in Ruby, and add some functionality to ActiveStorage::Blob:
Rails.configuration.to_prepare do
module ActiveStorageTouchRecordsAfterAnalyze
# Gives us some convenient shortcuts, like `prepended`
extend ActiveSupport::Concern
# When prepended into a class, define our callback
prepended do
after_update_commit :touch_all_records
end
# Iterate all attached records, and touch them
def touch_all_records
attachments.each do |attachment|
# there's a theoretical chance that the record is missing,
# therefore I .try() the touch.
attachment.record.try(:touch)
end
end
end
# After defining the module, call on ActiveStorage::Blob to prepend it in.
ActiveStorage::Blob.prepend ActiveStorageTouchRecordsAfterAnalyze
endThe above code goes into config/initializers/active_storage_touch_records_after_analyze.rb. After a reload, you can verify that it’s working by calling .analyze on one of your blobs.
to_prepare: Run after the initializers are run for all Railties (including the application itself), but before eager loading and the middleware stack is built. More importantly, will run upon every code reload in development, but only once (during boot-up) in production and test.
Note that I’m using a .to_prepare block around this initializer. This ensures that every bit of Rails is loaded enough, so I can safely inject my code. It also gives the benefit of being reloaded in development, which is quite helpful when making changes to initializers. For more in-depth information on the Rails boot-up stages, check out the official documentation.
Recent Posts
Writing a custom analyzer for ActiveStorage
Writing custom analyzers for your ActiveStorage blogs is not well documented, but quite easy. This is how I implemented a simple EDI file analyzer for my neverending hobby project.
Manjaro/Arch: transfer packages to another computer
How to make a backup of all installed packages on a Arch/Manjaro distro, and install them on a different machine.
Getting Norwegian characters on a US keyboard in Linux
Using a aluminum Apple keyboard in Linux, and getting accessible Norwegian characters like MacOS.