从acts_as_versioned迁移至paper_trail gem:版本数据复用咨询
从 acts_as_versioned 迁移到 PaperTrail 并复用现有版本数据
完全可以复用现有版本数据!虽然PaperTrail官方文档没直接给出迁移指引,但只要理清两个gem的版本表结构差异,写个迁移脚本就能顺利搞定。下面是具体操作步骤:
1. 先安装并初始化 PaperTrail
首先把PaperTrail加入项目:
# Gemfile gem 'paper_trail'
执行bundle install后,生成PaperTrail的初始迁移文件:
rails generate paper_trail:install
先运行这个迁移创建versions表:
rails db:migrate
2. 理清两个gem的版本结构差异
- acts_as_versioned:每个版本化模型对应独立的
[model]_versions表,直接存储原模型的所有字段,额外加item_id(关联原模型)、version(版本号)、created_at这几个字段。 - PaperTrail:默认用一张统一的
versions表,通过item_type区分不同模型,用object字段(序列化哈希)存储版本属性,还有event标记操作类型、whodunnit记录操作人(可选)。
3. 编写数据迁移脚本
假设你的原模型是Post,对应版本表是post_versions,生成一个新的迁移文件来处理数据迁移:
rails generate migration MigrateActsAsVersionedToPaperTrail
编辑这个迁移文件:
class MigrateActsAsVersionedToPaperTrail < ActiveRecord::Migration[5.2] def up # 迁移Post模型的版本数据 migrate_model_versions('Post', 'post_versions') # 有其他模型的话重复调用即可 # migrate_model_versions('Comment', 'comment_versions') end def down # 可选:如果需要回滚,把PaperTrail的数据导回原表 rollback_model_versions('Post', 'post_versions') end private def migrate_model_versions(model_name, version_table_name) model_class = model_name.constantize # 筛选出需要迁移的属性字段(排除acts_as_versioned自带的冗余字段) exclude_fields = ['id', 'item_id', 'version', 'created_at', 'updated_at'] attribute_fields = model_class.column_names - exclude_fields # 遍历原版本表的每条记录,转成PaperTrail格式插入 execute("SELECT * FROM #{version_table_name}").each do |old_version| # 构建属性哈希,用于序列化存储 object_data = attribute_fields.each_with_object({}) do |field, hash| hash[field] = old_version[field] end Version.create!( item_type: model_name, item_id: old_version['item_id'], # 第一个版本对应create操作,后续都是update event: old_version['version'] == 1 ? 'create' : 'update', object: YAML.dump(object_data), # PaperTrail默认用YAML序列化 created_at: old_version['created_at'] # 如果原表有操作人字段,比如user_id,可映射到whodunnit:whodunnit: old_version['user_id'] ) end end def rollback_model_versions(model_name, version_table_name) model_class = model_name.constantize attribute_fields = model_class.column_names Version.where(item_type: model_name).each do |version| object_data = YAML.load(version.object) # 还原原版本表的version编号逻辑 version_number = version.event == 'create' ? 1 : Version.where(item_type: model_name, item_id: version.item_id).where("created_at < ?", version.created_at).count + 1 attributes = { item_id: version.item_id, version: version_number, created_at: version.created_at }.merge(object_data.slice(*attribute_fields)) # 插入回原版本表 execute("INSERT INTO #{version_table_name} (#{attributes.keys.join(', ')}) VALUES (#{attributes.values.map { |v| quote(v) }.join(', ')})") end end end
注意点:
- 根据你的实际表结构调整
exclude_fields,确保只迁移模型的业务属性。 - 如果原版本表有记录操作人的字段,记得对应到PaperTrail的
whodunnit字段。 - 若原数据包含删除版本(destroy操作),需要单独判断并设置
event: 'destroy'。
4. 验证迁移结果
运行迁移:
rails db:migrate
然后在控制台测试数据是否正常:
post = Post.first post.versions.count # 应该和原post_versions的记录数一致 post.versions.last.reify # 尝试还原到旧版本,检查属性是否正确
5. 后续清理(可选)
确认迁移没问题后,可以清理旧代码和表:
- 移除Gemfile中的
acts_as_versioned并执行bundle clean - 删除模型中的
acts_as_versioned声明 - 生成迁移删除旧版本表:
rails generate migration DropActsAsVersionedTables
编辑迁移文件:
class DropActsAsVersionedTables < ActiveRecord::Migration[5.2] def up drop_table :post_versions # drop_table :comment_versions end def down # 如需回滚,需还原旧版本表的结构,可根据之前的迁移文件重建 end end
内容的提问来源于stack exchange,提问作者Bernardo Graça
相关产品推荐
相关产品推荐

