Rails中使用PaperTrail实现ActiveStorage附件版本控制的问题
解决方案
问题原因
默认情况下PaperTrail仅记录目标模型自身数据表的字段变化,has_one_attached :image对应的附件数据存储在active_storage_attachments和active_storage_blobs两张独立表中,不属于User模型的自有字段,因此不会被默认纳入版本记录。
方案1:直接跟踪ActiveStorage关联模型(无需新增字段)
这种方式不需要修改现有表结构,直接给ActiveStorage的关联模型开启PaperTrail跟踪即可:
- 新增PaperTrail初始化配置,路径为
config/initializers/paper_trail_active_storage.rb
Rails.application.config.to_prepare do # 给ActiveStorage附件模型开启版本跟踪 ActiveStorage::Attachment.has_paper_trail end
- 重启服务后,所有附件的增删改操作都会生成独立的版本记录,你可以通过如下方式查询指定用户的image附件历史版本:
user_id = 1 # 替换为目标用户ID attachment_versions = PaperTrail::Version.where( item_type: 'ActiveStorage::Attachment' ).where("object_changes LIKE ?", "%record_id: #{user_id}\n%record_type: User\n%name: image%") # 从版本中获取历史blob信息 attachment_versions.each do |version| # 旧版附件的blob_id可从版本的object/object_changes字段解析得到 blob_id = YAML.load(version.object)['blob_id'] rescue YAML.load(version.object_changes)['blob_id']&.last next unless blob_id # 拿到blob后即可获取对应的历史文件 old_blob = ActiveStorage::Blob.find(blob_id) puts "版本#{version.id}对应的文件地址:#{old_blob.url}" end
方案2:冗余存储Blob ID到User表(版本查询更便捷)
如果你希望User的版本记录直接携带附件标识,可以新增冗余字段同步blob_id:
- 生成迁移文件新增字段
rails g migration AddImageBlobIdToUsers image_blob_id:bigint rails db:migrate
- 修改User模型添加同步逻辑
class User < ApplicationRecord has_paper_trail has_one_attached :image # 附件变化时同步blob_id到自有字段,会自动被PaperTrail记录 before_save do self.image_blob_id = image.attached? ? image.blob_id : nil end end
- 后续查询和恢复历史附件的示例:
user = User.find(1) # 获取用户上一个版本的附件 old_version = user.versions.order(created_at: :desc).second old_blob_id = YAML.load(old_version.object)['image_blob_id'] if old_blob_id # 恢复附件到对应版本 user.image.attach(ActiveStorage::Blob.find(old_blob_id)) end
注意事项
- Rails默认的ActiveStorage Blob是不可变的,修改附件会生成新的Blob记录,因此通过blob_id可以唯一对应到历史文件内容
- 生产环境使用建议给PaperTrail的versions表的item_type、item_id字段加索引,提升查询效率
内容的提问来源于stack exchange,提问作者user16012143
相关产品推荐
相关产品推荐

