According to Rails (edge6.0
) guides, we can do maintenance work for ActiveStorage used in System Test and Integration Test by calling the following statement respectively
# System Test
FileUtils.rm_rf("#{Rails.root}/storage_test")
# Integration Test
FileUtils.rm_rf(Rails.root.join('tmp', 'storage'))
I want to know -
Are there any Rails built-in functions or rake commands or gems to do the following?
ActiveStorage::Blob
records those are no longer associated with any ActiveStorage::Attachment
record)ActiveStorage::Blob
record)I do not see any relevant rake tasks with rails --tasks
.
Currently, I am using
# remove blob not associated with any attachment
ActiveStorage::Blob.where.not(id: ActiveStorage::Attachment.select(:blob_id)).find_each do |blob|
blob.purge # or purge_later
end
and this script to clean orphan files (via rails console
)
# run these ruby statement in project rails console
# to remove the orphan file
include ActionView::Helpers::NumberHelper
dry_run = true
files = Dir['storage/??/??/*']
orphan = files.select do |f|
!ActiveStorage::Blob.exists?(key: File.basename(f))
end
sum = 0
orphan.each do |f|
sum += File.size(f)
FileUtils.remove(f) unless dry_run
end
puts "Size: #{number_to_human_size(sum)}"
Using Active Storage, an application can transform image uploads or generate image representations of non-image uploads like PDFs and videos, and extract metadata from arbitrary files.
I think the @Tun suggested solution in the question itself works properly. I write it in a Rake Tasks version in case this saves sometime to the next person coming here:
# Usage:
# - rake "remove_orphan_blobs"
# - rake "remove_orphan_blobs[false]"
desc "Remove blobs not associated with any attachment"
task :remove_orphan_blobs, [:dry_run] => :environment do |_t, args|
dry_run = true unless args.dry_run == "false"
puts("[#{Time.now}] Running remove_orphan_blobs :: INI#{" (dry_run activated)" if dry_run}")
ActiveStorage::Blob.where.not(id: ActiveStorage::Attachment.select(:blob_id)).find_each do |blob|
puts("Deleting Blob: #{ActiveStorage::Blob.service.path_for(blob.key)}#{" (dry_run activated)" if dry_run}")
blob.purge unless dry_run
end
puts("[#{Time.now}] Running remove_orphan_blobs :: END#{" (dry_run activated)" if dry_run}")
end
# Usage:
# - rake "remove_orphan_files"
# - rake "remove_orphan_files[false]"
desc "Remove files not associated with any blob"
task :remove_orphan_files, [:dry_run] => :environment do |_t, args|
include ActionView::Helpers::NumberHelper
dry_run = true unless args.dry_run == "false"
puts("[#{Time.now}] Running remove_orphan_files :: INI#{" (dry_run activated)" if dry_run}")
files = Dir["storage/??/??/*"]
orphan_files = files.select do |file|
!ActiveStorage::Blob.exists?(key: File.basename(file))
end
sum = 0
orphan_files.each do |file|
puts("Deleting File: #{file}#{" (dry_run activated)" if dry_run}")
sum += File.size(file)
FileUtils.remove(file) unless dry_run
end
puts "Size Liberated: #{number_to_human_size(sum)}#{" (dry_run activated)" if dry_run}"
puts("[#{Time.now}] Running remove_orphan_files :: END#{" (dry_run activated)" if dry_run}")
end
ActiveStorage::Blob.unattached.each(&:purge_later)
or just purge
if you don't want it to happen in the background.
Well there is a easier way now, not sure when it changed, but there is one caveat (it leaves the empty folder):
# rails < 6.1
ActiveStorage::Blob.left_joins(:attachments).where(active_storage_attachments: { id: nil }).find_each(&:purge)
# rails >= 6.1
ActiveStorage::Blob.missing(:attachments).find_each(&:purge)
This will delete the record in the database and delete the physical file on disk, but leave the folders the file existed in.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With