Website moved to Github

So, after 3 years my AWS reserved instance ran out. Considering I don't do nearly as much personal stuff as I used to, no point in buying a new one and Github is free (my favorite price point).

Mongoose 3.7.0 Released

I’ve spent the last 2 months interning at 10gen (MongoDB) working on Mongoose. It has been a lot of fun and I’ve learned a ton about Node.js, MongoDB, and building open source libraries. I’m going to save all of that for a different post though, and instead talk about the newest release of Mongoose.

Unstable

To start things off, this is an unstable release. This means that it contains potentially breaking changes or other major updates, and thus should probably not be used in production. You can tell this is an unstable release because of the middle digit. Starting from 3.7, odd middle digits mean unstable, even mean stable. This is identical to the Node.js and MongoDB versioning schemes.

Mquery

The largest change in this release is under the hood. We are now using the Mquery library to power querying in Mongoose. This allowed us to strip a lot of logic out of Mongoose and move it into a separate module. This should make things easier to maintain in the future and allows for the query engine to be reused elsewhere.

It does add some neat custom query functionality though:

var greatMovies = Movies.where('rating').gte(4.5).toConstructor();

// use it!
greatMovies().count(function (err, n) {
  console.log('There are %d great movies', n);
});

greatMovies().where({ name: /^Life/ }).select('name').find(function (err, docs) {
  console.log(docs);
});

Note: This feature is currently unreleased, but expected out in a 3.7.x update soon.

Benchmarks

We now have some basic benchmarks for Mongoose. We will definitely expand these in the future and these will allow us to start improving Mongoose’s performance and bring it more in line with the native driver. For the most part, Mongoose has almost every feature you can think of; however, it is a little slow when compared to the driver. These are our first step is working towards fixing that issue.

There are 7 different benchmarks:

  • casting
  • delete
  • insert
  • Multiple Operations
  • population
  • read
  • update

Each of these can be run on the command line using node, although it is recommended you have the MONGOOSE_DEV environment variable set to true when you run the benchmarks. We will also have graphs automatically generated using a new tool I built, gh-benchmarks. An example running on my development fork can be found here.

More Geospatial Support

We’ve added a lot in the geospatial area. First, we’ve added support for $geoWithin. This should largely be an unnoticed change, unless you’re using MongoDB before 2.4, then you’ll need to make sure and set Mongoose to continue using $within.

Next, we’ve added support for Haystack indices and the $geoNear command. This can be accessed directly off the model object.

var schema = new Schema({
  pos : [Number],
  type: String
});

schema.index({ "pos" : "2dsphere"});

mongoose.model('Geo', schema);

Geo.geoNear([9,9], { spherical : true, maxDistance : .1 }, function (err, results, stats) {
  console.log(results);
});

Finally, we’ve also added support for the $geoSearch command.

var schema = new Schema({
  pos : [Number],
  complex : {},
  type: String
});

schema.index({ "pos" : "geoHaystack", type : 1},{ bucketSize : 1});
mongoose.model('Geo', schema);

Geo.geoSearch({ type : "place" }, { near : [9,9], maxDistance : 5 }, function (err, results, stats) {
  console.log(results);
});

Aggregation Builder

We now have basic support for building aggregation queries the same way we build normal queries.

Example:

Model.aggregate({ $match: { age: { $gte: 21 }}}).unwind('tags').exec(cb);

See the change notes for more information.

Tons of Bug fixes and other minor enhancements

Check the change notes to see everything that has gone into this release.

Dealing with callbacks and for loops

So, let’s say you have a for loop iterating over an array. For each index, you need to run some asynchronous function and then assign the value to the index.

Should be easy, right? Maybe look something like:

for (var i=0; i < arr.length; i++) {                                            
  thing.doSomething(arr[i], function (err, val) {                               
    arr[i] = val;                                                               
  });                                                                           
} 

Well, that won’t quite work, because the value of I will change before the callbacks return and so you end up with a whole bunch of fail. Fortunately, there is a pretty easy way to get around this.

for (var i=0; i < arr.length; i++) {                                            
  (function(i) {                                                                
    thing.doSomething(arr[i], function (err, val) {                             
      arr[i] = val;                                                             
    });                                                                         
  })(i);                                                                        
}

So, if you’re like me, you learned how to use MongoDB and node/express/ect. through some random online tutorials and the reference docs. This is normally a pretty safe way to do things, but I have found in this case, there is one small issue that most don’t mention.

And normally you’ll end up with some code looking something like this:

UserProvider.prototype.addUser = function (userName, password, isAdmin, email, callback) {
    this.getUsersCollection(function (error, users) {
        if (error) {
            callback(error);
        } else {
            var salt = bcrypt.genSaltSync(10);
            var hash = bcrypt.hashSync(password, salt);
            users.insert({ userName: userName, password: hash, isAdmin: isAdmin, salt: salt, email: email }, function () {
                callback(null, users);
            });
        }
    });
};

And this looks fine, right? Don’t see any problems, and you might not notice any with code like this that isn’t frequently used and can probably be completed asynchronously.

However, there are cases where it is important that what we are inserting is actually in the database before we move on, and while this function seems to do that, it unfortunately does not. In this case, the anonymous callback passed to .insert() will be called immediately after the command has been sent to mongo, not after it has succeeded like we might be lead to believe. I find that most tutorials leave this part out and the reference documentation puts it pretty far down on the example list.

However, there is a really easy “fix” for this. (it isn’t a bug and can be quiet a powerful feature when you don’t need to wait) Simply add { safe :true } as a section parameter to .insert() (or .update()/.save() ) like this:

UserProvider.prototype.addUser = function (userName, password, isAdmin, email, callback) {
    this.getUsersCollection(function (error, users) {
        if (error) {
            callback(error);
        } else {
            var salt = bcrypt.genSaltSync(10);
            var hash = bcrypt.hashSync(password, salt);
            users.insert({ userName: userName, password: hash, isAdmin: isAdmin, salt: salt, email: email }, { safe : true}, function () {
                callback(null, users);
            });
        }
    });
};

And that’s it! The callback function will now only be called once the insert has either succeeded or failed.

Using Variables as fields in a Mongo Query from Node.js

Given the flexibility of mongoDB and javascript, it seems like a no-brainer that you should be able to use a variable to represent a field in a mongo query.

And you definitely can, although it isn’t quite as straightforward as putting your variable where the field is supposed to go.

Suppose we have the following code:

var searchField = 'name';
db.coll.find({searchField : 'value'});

This will not accomplish what we want to do. Instead of searching a field ‘name’ for ‘value’, it will search a field ‘searchField’ instead.

What will work though is if we do the following:

var searchField = 'name';
var searchParams = {};
searchParams[searchField] = 'value';
db.col.find(searchParams);

And that’s all there is to it. Just create an object, and use the variable name as a key, as shown above.