Reputation: 1208
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:
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 method
has_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.
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.
Upvotes: 3
Views: 1414
Reputation: 19358
Rails 7 (probably works on versions > 5)
Just to add to the options here... I think it is clean to put a line like this in config/application.rb
:
class Application < Rails::Application
config.to_prepare do
ActiveStorage::Blob.include Models::Blob
end
end
and then in lib/models/blob.rb
:
module Models
module Blob
def potato
'potato'
end
end
end
It works!
Upvotes: 1
Reputation: 93
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
Upvotes: 3
Reputation: 18521
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
Upvotes: 3