Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to clean up ActiveStorage record and storage folder of development environment?

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?

  1. remove the orphan blobs (ActiveStorage::Blob records those are no longer associated with any ActiveStorage::Attachment record)
  2. remove the orphan files (files those are no longer associated with any 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)}"
like image 510
Tun Avatar asked Oct 08 '20 14:10

Tun


People also ask

How activestorage works?

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.


Video Answer


3 Answers

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
like image 199
fguillen Avatar answered Nov 04 '22 11:11

fguillen


ActiveStorage::Blob.unattached.each(&:purge_later)

or just purge if you don't want it to happen in the background.

like image 40
estani Avatar answered Nov 04 '22 09:11

estani


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.

like image 37
dft Avatar answered Nov 04 '22 09:11

dft