As MongoDB database access and initialization is asynchronous on Node.js, I would like to define one module per collection that exports wrapped db calls after db initialization.
Such a "Cars.model.js" module looks like that:
var db = require("mongodb");
db.collection("cars", function(err, col) {
exports.getCars = function(callback) {
col.find({}, callback);
};
});
so that other modules can run:
var carModel = require("Cars.model.js").getCars;
getCars(err, cars) {
// (do something with cars here...)
};
It happened to me that getCars
was undefined, because db access was not yet initialized at the time my second module was run.
How do you deal with creating such asynchronous db models?
You cannot write to exports
after you've left the file. You must be blocking. To avoid being blocking I would use lazy loading of resources.
var carCol;
var carEmitter = new require("events").EventEmitter;
exports.getCars = function(callback) {
// if no car collection then bind to event
if (carCol === undefined) {
carEmitter.on("cars-ready", function() {
callback(carCol);
});
} else {
// we have cars, send them back
callback(carCol);
}
}
db.collection("cars", function(err, col) {
// store cars
carCol = col;
// tell waiters that we have cars.
carEmitter.emit("cars-ready");
});
Use event emitters to emulate lazy loading. You may want to generalize to a LazyLoadedCollection
class/object to make the code neater / more DRY.
I'm a newbe so don't be mad at me...
I'm using promises to do so:
var db = require("mongodb"),
Q = require('q'),
getCarsDefer = Q.defer();
exports.getCars = getCarsDefer.promise;
db.collection("cars", function(err, col) {
if(err){
getCarsDefer.reject(err);
}else{
getCarsDefer.resolve(Q.ninvoke(col,'find'));
};
});
So you can get your cars like that:
var carModel = require("Cars.model.js").getCars;
getCars.then(cars) {
// (do something with cars here...)
};
It this is a bad idea, please let me know because it's what i'm doing right now.
I believe this simple solution works: replace the asynchronous call to getCars()
by a synchronous call to a collection cache that would be populated before models can be called.
The main.js application starter module:
var db = require("mongodb");
exports.collectionCache = {};
db.collection("cars", function(err, col) {
exports.collectionCache["cars"] = col;
// ready => start application logic (incl. models) here
});
Thus "Cars.model.js" would look like that:
var col = require("main").collectionCache; // populated
exports.getCars = function(callback) {
col["cars"].find({}, callback);
};
This solution moves the asynchronous problem from models to the root database model, making models simpler to develop.