diff --git a/docs/guide/index.rst b/docs/guide/index.rst index 95a9b92b..62de0ecf 100644 --- a/docs/guide/index.rst +++ b/docs/guide/index.rst @@ -16,3 +16,4 @@ User Guide text-indexes logging-monitoring mongomock + migration diff --git a/docs/guide/migration.rst b/docs/guide/migration.rst new file mode 100644 index 00000000..c0c69d08 --- /dev/null +++ b/docs/guide/migration.rst @@ -0,0 +1,70 @@ +============================== +Handling migration +============================== + +The structure of your documents and their associated mongoengine schemas are likely +to change over the lifetime of an application. This section provides guidance and +recommendations on how to deal with migrations. + +Due to the very flexible nature of mongodb, migrations of models aren't trivial and +for people that know about `alembic` for `sqlalchemy`, there is unfortunately no equivalent +library that will manage the migration in an automatic fashion for mongoengine. + +First of all, let's take a simple example of model change and review the different option you +have to deal with the migration. + +Let's assume we start with the following schema and save an instance: + +.. code-block:: python + + class User(Document): + name = StringField() + + User(name=username).save() + + # print the objects as they exist in mongodb + print(User.objects().as_pymongo()) # [{u'_id': ObjectId('5d06b9c3d7c1f18db3e7c874'), u'name': u'John'}] + +On the next version of your application, let's now assume that a new field `enabled` gets added to the +existing User model with a `default=True`. Thus you simply update the `User` class to the following: + +.. code-block:: python + + class User(Document): + name = StringField(required=True) + enabled = BooleaField(default=True) + +Without migration, we now reload an object from the database into the `User` class and checks its `enabled` +attribute: + +.. code-block:: python + + assert User.objects.count() == 1 + user = User.objects().first() + assert user.enabled is True + print(User.objects(enabled=True).count()) # 0 ! uh? + print(User.objects(enabled=False).count()) # 0 ! uh? + + # but this is consistent with what we have in database + print(User.objects().as_pymongo().first()) # {u'_id': ObjectId('5d06b9c3d7c1f18db3e7c874'), u'name': u'John'} + assert User.objects(enabled=None).count() == 1 + +As you can see, even if the document wasn't updated, mongoengine applies the default value seemlessly when it +loads the pymongo dict into a `User` instance. At first sight it looks like you don't need to migrate the +existing documents when adding new fields but this actually leads to inconsistencies when it comes to querying. + +In fact, when querying, mongoengine isn't trying to account for the default value of the new field and so +if you don't actually migrate the existing documents, you are taking a risk that querying/updating +will be missing relevant record. + +When adding fields/modifying default values, you can use any of the following to do the migration +as a standalone script: + +.. code-block:: python + + User.objects().update(enabled=True) + # or + user_coll = User._get_collection() + user_coll.update_many({}, {'$set': {'enabled': True}}) + +