Mongoose version 3 almost here and comes packed with new features, bugfixes, and performance improvements. This series of posts will cover the goodies and any important API changes you should be aware of.
versioning
This post will cover document versioning. To explain what document versioning is and why it is important, we first need to cover the state of things today in the 2.x branch.
Let’s say we have a blog post schema that contains an array of sub-documents, say comments:
https://gist.github.com/2961869
Now, suppose you get busy and write up a really great blog post, one you’re very proud of, and post it on hacker news. Surprise! Because of your amazing writing skills and content your post starts attracting a large number of views and comments.
What does our code look like to add a comment? It’s simple enough, we’ll just retreive this post and push a new comment object to it’s comments array:
https://gist.github.com/2961877
I skipped all the form submission steps but you can see how we’re saving or comment. So far so good.
Now suppose a commenter realizes they posted something completely stupid and wish to fix their mistake:
https://gist.github.com/2961883
To see how this could be problematic we need to take a closer look at the underlying operation used to update the comment. When post.save()
is executed, an update
is issued to MongoDB that looks like the following:
posts.update({ _id: postId }
, { $set: { 'comments.3.body': updatedText }})
Notice comments.3.body
, this is called positional notation. This tells MongoDB to set the body of the comment in the comments array at index position 3 to the updated text.
Can you think of anything wrong with this? If another commenter on this blog post decides to delete their comment between the time this document was retrieved and the time the update sent, comments.3.body
might specify the wrong sub-document since the position of our comment in the array may have changed. More generally, if any operation on our comments array causes the index of any comment to change we can get into hot water.
To mitigate this issue, Mongoose v3 now adds a schema-configurable version key to each document. This value is atomically incremented whenever a modification to an array potentially changes any array’s elements position. This value is also sent along in the where
clause for any updates that require the use of positional notation. If our where
clause still matches the document, it ensures that no other operations have changed our array elements position and it is ok to use use positional syntax.
So back to our previous example. Our update command in v3 now looks like the following:
posts.update({ _id: postId, __v: verionNumber }
, { $set: { 'comments.3.body': updatedText }})
The version number is included in the where
clause. If no document is found due to the version no longer matching or the document was removed from the collection, an error is returned to the callback that you can handle in your application:
post.save(function (err) {
console.log(err); // Error: No matching document found.
});
increment
In version 3, documents now have an increment()
method which manually forces incrementation of the document version. This is also used internally whenever an operation on an array potentially alters array element position. These operations are:
$pull
$pullAll
$pop
$set of an entire array
$push
$pushAll
$addToSet
changing the version key
The version key is customizable by passing the versionKey
option to the Schema constructor:
new Schema({ .. }, { versionKey: 'myVersionKey' });
Or by setting the option directly:
schema.set('versionKey', 'myVersionKey');
disabling
If you don’t want to use versioning in your schema you can disable it by passing false for the versionKey
option.
schema.set('versionKey', false);
Try it today!
Go ahead and try out version 3 in your projects today and report any bugs you find or just give your feedback. The code is on github and available on npm under mongoose@3.0.0alpha1.