Because this took me a long time to figure out I thought I'd share it with people in case other people get stuck on the same problem.

The problem is that Mongoose doesn't support DBRefs. A DBRef is just a little sub structure with a two keys: $ref and $id where $id is an ObjectId instance. Here's what it might look like on the mongodb shell:


> db.questions.findOne();
{
       "_id" : ObjectId("4d64322a6da68156b8000001"),
       "author" : {
               "$ref" : "users",
               "$id" : ObjectId("4d584fb86da681668b000000")
       },
       "text" : "Foo?",
       ...
       "answer" : "Bar"
       "genre" : {
               "$ref" : "question_genres",
               "$id" : ObjectId("4d64322a6da68156b8000000")
       }
}

DBRefs are very convenient because various wrappers on drivers can do automatic cross-fetching based on this. For example, with MongoKit I can do this:


for question in db.Question.find():
   print question.author.first_name

If we didn't have DBRefs you'd have to do this:


for question in db.Question.find():
   author = db.Authors.findOne({'_id': question.author})
   print author.first_name

Anyway, the problem Mongoose has is that it doesn't support DBRefs so when you define its structure you have to do this:


var QuestionSchema = new mongoose.Schema({
  text     : String
  , answer      : String
  ...
  , genre: {},
  , author : {}
});
mongoose.model('Question', QuestionSchema);
var Question = mongoose.model('Question', 'questions');

Now, that sucks but I can learn to live with it. Giving this schema here's how you can work with the defined model from an existing database:


Question.find({}, function(err, docs) {
  docs.forEach(function(each) {
     each.findAuthor(function(err, item) {
        console.log(item.doc.first_name);
     });     
  });
});

(note: I have no idea with this "doc" struct is but perhaps the gods of Mongoose can explain that one)

So, given that I can't work with DBRefs in Mongoose; how do to mock it? Here's how:


    var author = new models.Author();
    author.username = "peter";
    author.save(function(err) {
        var question = new models.Question();
        // fake a dbref
        question.author = {
           "$ref" : "users",
           "oid" : user._id
        };
        question.save(function(err) {
           question.findAuthor(function(err, u) {
              assert.ok(u);
              assert.ok(!err);
              test.equal(u.doc.first_name, 'peter');
              test.done();
           });
        });
     });

That's the magic. This way you can pretend, in your tests, that you have objects with proper DBRefs. It feels strangely convoluted and hackish but I'm sure once I've understood this better there might be a better way.

Perhaps I could have spent the time it took to figure this out and to write this blog I could have stepped up and written a DBRef plugin for Mongoose.

Comments

Your email will never ever be published.

Related posts