Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Patch Blob model in Active Storage

I want to patch Rails 6.0 to include part of this PR: https://github.com/rails/rails/commit/4dba136c83cc808282625c0d5b195ce5e0bbaa68 I'm only using direct uploads so I'm only patching create_before_direct_upload! at the moment. Here is what I have tried:

  1. In initializers/active_storage.rb
module BlobOverride
  class << self
    def create_before_direct_upload!(key: nil, filename:, byte_size:, checksum:, content_type: nil, metadata: nil)
      puts "In Blob Override Patch"
      byebug
      create! key: key, filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, metadata: metadata
    end
  end
end

 ActiveStorage::Blob.prepend(BlobOverride)

This returns the undefined methodhas_one_attached'` error, which I tracked down to a github issue here: https://github.com/rails/rails/issues/38876 Which basically says you can't use load the model from the initializer.
2. I then tried loading the module this way:

ActiveSupport.on_load(:active_storage_blob) do
  ActiveStorage::Blob.prepend(BlobOverride)
end

And I didn't get an error but my patch wasn't hit.
3. I tried this:

Rails.application.config.to_prepare do
  require 'active_storage/blob'
  ActiveStorage::Blob.class_eval do
    def create_before_direct_upload!(key: nil, filename:, byte_size:, checksum:, content_type: nil, metadata: nil)
      puts "in create before direct upload patch"
      byebug
      create! key: key, filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, metadata: metadata
    end
  end
end

No error, patched method wasn't hit.

TLDR

How do I patch the blob model on active storage to support a custom key? The standard monkey patching isn't working for some reason.

like image 444
leenyburger Avatar asked Dec 29 '25 12:12

leenyburger


2 Answers

This applies for Rails 6.1:

ActiveRecord now has a thing called has_secure_token, which ActiveStorage is using on Blob. This has_secure_token is setting a before_create with the following code:

before_create { send("#{attribute}=", self.class.generate_unique_secure_token(length: length)) unless send("#{attribute}?") }

This in turn sets self[:key] to be a secure_token

When it's being called like this, the self[:key] method only sets it if it doesn't exist

ActiveStorage::Blob.class_eval do
  def key
    self[:key] ||= 'your custom key'
  end
end

I fixed this by doing the following in config/application.rb

class Application < Rails::Application
  # ...
  # ...
  # ...

  Rails.application.config.to_prepare do
    require 'active_storage/blob'
    class ActiveStorage::Blob
      def self.generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH)
        gust = SecureRandom.base36(length)
        epoch = Time.now.to_i.to_s
        checksum = Digest::MD5.hexdigest(gust + epoch)

        folder = checksum.chars.first(9).each_slice(3).to_a.map(&:join).join('/')

        "#{folder}/#{gust}"
      end
    end

    # Rails 5 doesn't have secure_token for Blob, so this will monkey patch it
    ActiveStorage::Blob.class_eval do
      def key
        self[:key] ||= generate_key_with_folder
      end

      def generate_key_with_folder
        gust = self.class.generate_unique_secure_token
        epoch = Time.now.to_i.to_s
        checksum = Digest::MD5.hexdigest(gust + epoch)

        folder = checksum.chars.first(9).each_slice(3).to_a.map(&:join).join('/')

        "#{folder}/#{gust}"
      end
    end
  end
end
like image 184
Arian Faurtosh Avatar answered Jan 01 '26 03:01

Arian Faurtosh


Thanks to @Arian's answer, here is my implementation in a Rails 5 app.

I created an initializer config/initializers/active_storage_custom_key.rb with the following code

Rails.application.config.to_prepare do
  require 'active_storage/blob'
  ActiveStorage::Blob.class_eval do
    def key
      self[:key] ||= File.join(self.class.generate_unique_secure_token, filename.to_s)
    end
  end
end

This generates keys with the form SZJLX7fLpsv1dzzj2PhRg7ve/my_filename.jpg

like image 25
gabriel Avatar answered Jan 01 '26 04:01

gabriel



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!