如何添加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'
相关产品推荐
相关产品推荐

