23/4/2013 Update:
A change implementing promise support for the create
function was just committed to GitHub, and should be available in the 3.7 release of Mongoose. Sweet!
Just wanted to share a nice little technique I discovered when working on a project using the wonderful Mongoose library to interact with a MongoDB database in Node.js. I was writing some integration tests, and needed to reset and seed the database from a set of JSON files before running each test. I really wanted to do this through my existing Mongoose models, so that I could get all the seed data validated before running my tests. Since there also are relations between my different collections I needed to insert the collections in a certain order, so my first attempt quickly devolved into a textbook example of the good ol' pyramid of doom:
User.remove({}, function(err) {
if(err) { return done(err); }
Exercise.remove({}, function(err) {
if(err) { return done(err); }
WorkoutTemplate.remove({}, function(err) {
if(err) { return done(err); }
User.create(
require('users.json'),
function(err) {
if(err) { return done(err); }
Exercise.create(
require('exercises.json'),
function(err) {
if(err) { return done(err); }
WorkoutTemplate.create(
require('workoutTemplates.json'),
function(err) {
if(err) { return done(err); }
else { return done(); }
});
});
});
});
});
});
In the above code snippet User
, Exercise
, and WorkoutTemplate
are my mongoose models, and since my WorkoutTemplate
schema references my Exercise
schema I need to insert data in that specific order. Using Mongoose's remove
function with an empty object as the first argument will clear everything from the related collection. Furthermore, the create
function will attempt to do a batch insert of the array you hand it. However, as you can see this quickly devolved into callback hell, and that was with just three different collections! Fortunately, I discovered that the exec
function, which you can call on query objects, returns a promise object. This means I can just make a pretty, little promise chain instead of using callbacks.
There is just one obstacle to this... The create
function does not return a query object, meaning we can't call exec
on it to return a promise... I decided to rectify this by attaching a new function on the base mongoose Model which basically just wraps the create
function, but actually returns a promise object.
var mongoose = require('mongoose');
mongoose.Model.seed = function(entities) {
var promise = new mongoose.Promise;
this.create(entities, function(err) {
if(err) { promise.reject(err); }
else { promise.resolve(); }
});
return promise;
};
So, finally I am able to create a nice and neat promise chain in the beforeEach
function of my test suite. Voilà!
beforeEach(function(done) {
// ...
// Reset collections
User.remove().exec()
.then(function() {
return Exercise.remove().exec()
})
.then(function() {
return WorkoutTemplate.remove().exec()
})
// Seed
.then(function() {
return User.seed(
require('users.json'));
})
.then(function() {
return Exercise.seed(
require('exercises.json'));
})
.then(function() {
return WorkoutTemplate.seed(
require('workoutTemplates.json')); })
// Finito!
.then(function() {
done();
}, function(err) {
return done(err);
});
// ...
});