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.
Initial Data Structure
class User
include Mongoid::Document
field :name
references_many :sales
end
class Sale
include Mongoid::Document
field :price, :type => Integer
referenced_in :user
end
Now with Sale embedded in User
class User
include Mongoid::Document
field :name
embeds_many :sales
end
class Sale
include Mongoid::Document
field :price, :type => Integer
embedded_in :user, :inverse_of => :sales
end
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.
- I am using mongoid_rails_migrations in this example
About the Author