Reputation: 19873
How can I run a migration to change the type of a field in Mongoid/MongoDB without losing any data?
In my case I'm trying to convert from a BigDecimal (stored as string) to an Integer to store some money. I need to convert the string decimal representation to cents for the integer. I don't want to lose the existing data.
I'm assuming the steps might be something like:
amount2
amount
to the right value for amount2
amount
and there is no downtime from the users perspective)amount
fields that could have changed in the last few minutesamount
and rename amount2
to amount
amount
to be an integerIt looks like Mongoid offers a rename
method: http://mongoid.org/docs/persistence/atomic.html#rename
But I'm a little confused how this is used. If you have a field named amount2
(and you've already deleted amount
), do you just run Transaction.rename :amount2, :amount
? Then I imagine this immediately breaks the underlying representation so you have to restart your app server after that? What happens if you run that while amount
still exists? Does it get overwritten, fail, or try to convert on it's own?
Thanks!
Upvotes: 2
Views: 3250
Reputation: 19873
Ok I made it through. I think there is a faster way using the mongo console with something like this: MongoDB: How to change the type of a field?
But I couldn't get the conversion working, so opted for this slower method in the rails console with more downtime. If anyone has a faster solution please post it.
amount2
amount
to the right value for amount2
in a console or rake task
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
Note that .all.each works fine (you don't need to use .find_each or .find_in_batches like regular activerecord with mysql) because of mongodb cursors. It won't fill up memory as long as the identity_map is off.
take the site down for maintenance, run the migration one more time to capture any amount fields that could have changed in the last few minutes (something like Transaction.where(:updated_at.gt => 1.hour.ago).each_with_index...
comment out field :amount, type: BigDecimal
in your model, you don't want mongoid to know about this field anymore, and push this code
Mongoid.identity_map_enabled = false
Transaction.all.each_with_index do |t,i|
puts i if i%1000==0
t.rename :amount2, :amount
end
This is atomic and doesn't require a save on the model.
field :amount, type: Integer
As mentioned I think there is a better way, so if anyone has some tips please share. Thanks!
Upvotes: 4