Embedding Mongoid documents and data migrations

When first starting out with mongodb, it’s easy to make the wrong decision on whether to embed a document or not. Even if you made the correct decision at that moment, changing requirements may force you into a migration. So how do you migrate existing data when transitioning from a standalone document to an embedded document? This is what I came up with.

Now with Sale embedded in User

Migrating Sales Data

class EmbedSalesInUsers < Mongoid::Migration
def self.up
# pull your existing data into memory
# consider batching for large data sets
# Note that you must call query methods on the object you are migrating
# for this method to work (i.e. you can not pull via User#sales)
sales_attributes = while_stand_alone_doc(Sale) do
Sale.all.map(&:attributes)
end
# now when you save your data, your fields will be embedded
sales_attributes.each do |attributes|
user = User.find(attributes[:user_id])
user.sales << Sale.new(:price => attributes[:price])
end
# remove all the documents from the original collection
while_stand_alone_doc(Sale) do
Sale.destroy_all
end
end
def self.while_stand_alone_doc(klass)
# by changing the Mongoid::Document.embedded you can temporarily
# modify which collection Mongoid looks to for your model's data store
begin
klass.embedded = false
yield
ensure
klass.embedded = true
end
end
end

There are a couple things to note here.

The embedded flag in Mongoid::Document is not documented so it could easily change. This was working as of 2.0.0.beta.20

When you create the new embedded document, make sure you pass only the attributes you care about. Passing all attributes will add things that you no longer need like user_id in this case. (For clarity, attributes you assign will be persisted, though you will only have setters and getters for the fields you explicitly define in your document.