You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails中destroy_all无法清理S3多附件,destroy可正常执行

问题:使用Tour.destroy_all时无法清理S3中的多附件图片

背景信息

基于Rails API开发的系统中,Tour模型包含两种ActiveStorage附件:单文件的cover_image和多文件的images,创建流程正常,但销毁记录时出现附件清理不一致的问题。

控制器创建流程代码

def create_tour
  if(@params[:tour_cover_image].present?)
    tour_images = @params.select{ |key, _| key.to_s.start_with?('tour_image-') }
    tour = Tour.new(@tour_params)
    tour.save!
    tour.cover_image.attach(
      io: File.open(@params[:tour_cover_image].tempfile),
      filename: @params[:tour_cover_image].original_filename,
      content_type: @params[:tour_cover_image].content_type,
      key: "public/tours/#{tour.id}/cover_image/#{SecureRandom.uuid}-#{@params[:tour_cover_image].original_filename}"
    )
    tour_images.each do |key, image|
      tour.images.attach(
        io: File.open(image.tempfile),
        filename: image.original_filename,
        content_type: image.content_type,
        key: "public/tours/#{tour.id}/images/#{SecureRandom.uuid}-#{image.original_filename}"
      )
    end
  else
    tour = Tour.new(@tour_params.except(:images, :cover_image))
    tour.save!
  end
  tour
end

清理数据的Rake任务

namespace :tours do
  desc "This will destroy all tours and their dependencies"
  task destroy_all: :environment do
    Tour.destroy_all
    puts "All tours and their dependencies have been destroyed"
  end
end

初始Tour模型(带销毁回调)

class Tour < ApplicationRecord
  has_many :tour_vehicles_tours, dependent: :destroy
  has_many :tour_vehicles, through: :tour_vehicles_tours
  has_many :schedule_stop, dependent: :destroy
  belongs_to :user
  has_one_attached :cover_image
  has_many_attached :images

  before_destroy :purge_images

  def purge_images
    images.purge
    cover_image.purge
  end
end

问题现象

  • 执行Tour.destroy_all(通过Rake任务)时,purge_images回调触发,但仅cover_image会从S3被移除,images未被清理,且回调中images.attached?返回false。
  • 手动执行Tour.last.destroy销毁单条记录时,回调正常工作,cover_image和images均能从S3被清理。

原因分析

destroy_all会批量加载记录并逐个调用destroy,但批量场景下ActiveStorage的has_many_attached关联不会自动预加载;加上before_destroy回调时机在记录销毁前,此时关联的附件记录可能已被标记为待删除,导致查询不到images,自然无法执行purge。

而单条记录调用destroy时,ActiveRecord会确保关联数据被正确加载,因此images能正常访问并被清理。

可行解决方案

改用after_destroy_commit回调,通过Blob的key前缀直接查询并清理对应附件,绕开关联加载的问题:

class Tour < ApplicationRecord
  has_one_attached :cover_image
  has_many_attached :images

  after_destroy_commit :purge_images

  def purge_images
    begin
      if cover_image.attached?
        cover_image.purge
        Rails.logger.info "Successfully purged cover image for Tour #{id}"
      end
      # 通过key前缀定位当前Tour下的所有图片Blob
      image_blobs = ActiveStorage::Blob.where("key LIKE ?", "public/tours/#{id}/images/%")
      if image_blobs.present?
        image_blobs.each(&:purge)
        Rails.logger.info "Successfully purged #{image_blobs.size} images for Tour #{id}"
      end
    rescue StandardError => e
      Rails.logger.error "Failed to purge images for Tour #{id}: #{e.message}"
    end
  end
end

该方案直接通过存储路径定位附件,确保批量销毁时也能正确清理S3中的文件。

内容的提问来源于stack exchange,提问作者Thiago David

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 03:46:14