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

如何添加ActiveStorage附件而不触发ActiveRecord回调?

解决ActiveStorage附件更新触发after_save无限循环的问题

针对你遇到的问题,有几种可行的方案可以在更新ActiveStorage附件时不触发模型的after_save回调,避免无限循环:

方案1:直接操作ActiveStorage关联表,绕过模型回调

直接对active_storage_attachments表进行CRUD操作,使用update_columns(不会触发任何模型回调)来关联新的Blob:

class ComplicatedModel < ActiveRecord::Base
  has_one_attached :activestorage_cached_json_response

  after_save do 
    # 生成JSON字符串(注意:as_json返回Hash,需转成字符串)
    json_string = self.as_json.to_json
    # 创建并上传Blob到存储服务
    blob = ActiveStorage::Blob.create_and_upload!(
      io: StringIO.new(json_string),
      filename: "ComplicatedModel_#{self.id}.json",
      content_type: 'application/json'
    )

    # 查找现有关联记录,更新Blob ID;没有则创建新关联
    attachment = self.active_storage_attachments.find_by(name: :activestorage_cached_json_response)
    if attachment
      attachment.update_columns(blob_id: blob.id)
    else
      self.active_storage_attachments.create!(
        name: :activestorage_cached_json_response,
        blob_id: blob.id,
        record_type: self.class.name,
        record_id: self.id
      )
    end

    # 可选:清理旧Blob(避免存储冗余)
    activestorage_cached_json_response.purge_later if activestorage_cached_json_response.attached? && activestorage_cached_json_response.blob_id != blob.id
  end
end

方案2:临时跳过回调,操作完成后恢复

通过skip_callback临时关闭当前的after_save回调逻辑,完成附件更新后再恢复:

class ComplicatedModel < ActiveRecord::Base
  has_one_attached :activestorage_cached_json_response

  after_save :update_cached_json_response

  private

  def update_cached_json_response
    # 临时跳过当前回调,防止循环
    self.class.skip_callback(:save, :after, :update_cached_json_response)

    begin
      # 先删除旧附件
      activestorage_cached_json_response.purge if activestorage_cached_json_response.attached?
      # 上传新的JSON缓存
      activestorage_cached_json_response.attach(
        io: StringIO.new(self.as_json.to_json),
        filename: "ComplicatedModel_#{self.id}.json",
        content_type: 'application/json'
      )
    ensure
      # 无论操作成功与否,恢复回调
      self.class.set_callback(:save, :after, :update_cached_json_response)
    end
  end
end

方案3:直接创建Attachment记录,不通过模型关联方法

不调用模型的attach方法,直接创建ActiveStorage::Attachment记录,这样不会触发模型的save操作:

class ComplicatedModel < ActiveRecord::Base
  has_one_attached :activestorage_cached_json_response

  after_save do 
    json_string = self.as_json.to_json
    blob = ActiveStorage::Blob.create_and_upload!(
      io: StringIO.new(json_string),
      filename: "ComplicatedModel_#{self.id}.json",
      content_type: 'application/json'
    )

    # 删除旧的关联附件
    ActiveStorage::Attachment.where(
      record_type: self.class.name,
      record_id: self.id,
      name: :activestorage_cached_json_response
    ).destroy_all

    # 直接创建新关联,不触发模型save
    ActiveStorage::Attachment.create!(
      record_type: self.class.name,
      record_id: self.id,
      name: :activestorage_cached_json_response,
      blob_id: blob.id
    )
  end
end

注意事项

  • 原代码中StringIO.new(self.as_json)是错误的,因为as_json返回的是Ruby Hash,需要用to_json转换成JSON字符串。
  • 如果生成JSON的过程确实非常耗时,建议把这部分逻辑放到后台任务(比如Sidekiq)中执行,避免阻塞请求响应。

内容的提问来源于stack exchange,提问作者dB'

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 05:27:24