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

Solution 1:

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

Solution 2:

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.