更改Mongoid中的字段类型而不会丢失数据

时间:2012-02-22 05:29:13

标签: ruby-on-rails mongodb mongoid

如何在不丢失任何数据的情况下运行迁移以更改Mongoid / MongoDB中字段的类型?

在我的情况下,我试图将BigDecimal(存储为字符串)转换为Integer来存储一些钱。我需要将字符串十进制表示转换为整数的美分。我不想丢失现有数据。

我假设步骤可能是这样的:

  1. 使用新名称创建新的整数字段,例如amount2
  2. 部署到生产并运行迁移(或rake任务),将每个amount转换为amount2的正确值
  3. (这整个现有代码仍在使用amount,并且从用户的角度来看没有停机时间)
  4. 停用网站进行维护,再次运行迁移以捕获最近几分钟内可能发生更改的所有amount字段
  5. 删除amount并将amount2重命名为amount
  6. 部署新代码,期望amount为整数
  7. 恢复网站
  8. 看起来Mongoid提供rename方法:http://mongoid.org/docs/persistence/atomic.html#rename

    但我有点困惑如何使用它。如果您有一个名为amount2的字段(并且您已经删除了amount),那么您是否只运行Transaction.rename :amount2, :amount?然后我想这会立即打破基础表示,所以你必须在那之后重启你的应用服务器?如果您在amount仍然存在时运行该怎么办?是否会被覆盖,失败或尝试自行转换?

    谢谢!

1 个答案:

答案 0 :(得分:3)

好的,我完成了。我认为有一种更快的方式使用mongo控制台,如下所示: MongoDB: How to change the type of a field?

但是我无法使转换工作,因此在rails控制台中选择了这种速度较慢的方法,停机时间更长。如果有人有更快的解决方案,请发布。

  • 使用新名称创建新的整数字段,例如amount2
  • 在控制台或佣金任务中将每个amount转换为amount2的正确值

Mongoid.identity_map_enabled = false
Transaction.all.each_with_index do |t,i|
  puts i if i%1000==0
  t.amount2 = t.amount.to_money
  break if !t.save
end

请注意.all.each工作正常(因为mongodb游标,你不需要使用.find_each或.find_in_batches,如使用mysql的常规activerecord)。只要identity_map关闭,它就不会填满内存。

  • 关闭网站进行维护,再次运行迁移以捕获最近几分钟内可能发生变化的任何金额字段(类似Transaction.where(:updated_at.gt => 1.hour.ago).each_with_index...

  • 在您的模型中注释field :amount, type: BigDecimal,您不希望mongoid再次知道此字段,并推送此代码

  • 现在运行另一个脚本来重命名您的列(它会覆盖进程中任何旧的BigDecimal字符串值)。您可能需要评论模型上对旧字段的任何验证。

Mongoid.identity_map_enabled = false
Transaction.all.each_with_index do |t,i|
  puts i if i%1000==0
  t.rename :amount2, :amount
end

这是原子的,不需要在模型上保存。

  • 更新您的模型以反映新列类型field :amount, type: Integer
  • 部署并恢复网站

如前所述,我认为有更好的方法,所以如果有人有一些提示请分享。谢谢!