Karan
Karan

Reputation: 1151

Recursion on Tree like structure of mongoose documents

I need help pulling out objects of objects from my database through a get request, so I can display all of it on my web page, but this test I ran already failed, debugging it, it just runs forever and the JSON is never received. I think my logic might be messed up somewhere. I'm not sure if I should use async for this, it does look like callback hell, and now thinking about it I probably should, but I don't know if thinking in optimization if this is a decent way, I usually start from the worst case, and with the attempt I ran below what I was aiming for was around O(N) since there are N total documents and I'm just traversing down a recursion tree, with some additional queries for finding the initial documents and what not so, N+X (where x is a additional amount of queries < N). But I feel as if I'm missing out on something conceptually as if there might be a better way.

I have a schema, something like this

Doc {
title : string,
author : string,
subDoc : [{type: mongoose.Schema.Types.ObjectId, ref : 'Doc'}]
}

So there are documents I generate and everytime I generate these documents I have subdocuments that can exist for these documents, and even those subdocuments can have subdocuments. Then we end up with some tree-like structure. However I wan't to display the Document > then its sub_doc1 > all of its subdocs... etc > its sub doc2 > all of its sub doc..> etc..

However I don't know if what I'm thinking is efficient and if it is worth even doing with this many queries.

app.get('/docs/:doc/subDocTree', function(req, res) {
    var array = [];
    var id = req.params.doc;
    var num_proc = 0;
    console.log(id);
    Doc.findById(id, function(err, doc) {
        console.log(doc);
        for (var x = 0; x < doc.subDoc.length; x++) {
            Doc.findById(form.subDoc[x], function(err, subDoc) {
                populate(subDoc);
            });
        }
        num_proc = num_proc + 1;
        if (num_proc == Doc.subDoc.length) {
            res.json(array);
            array.length = 0;
        }
    });
});

function populate(docs) {
    var num_proc = 0;
    Form.findById(docss, function(err, doc) {
        if (doc.subform.length != 0) {
            //console.log(form);
            var total = doc.subDoc.length;
            for (var i = 0; i < total; i++) {
                array.push(doc.subDoc[x]);
                num_proc = num_proc + 1;
                populate(subDoc[x]);
            }
        }
    });
}

I thought that searching for the document first, then searching for all of its subdocuments, then recursively checking with the populate method for any subdocs of subdocs would work. However I never end up recieving the JSON, and through debugging with print statements/console.logs, I still couldn't figure out where I went wrong.

Upvotes: 1

Views: 3144

Answers (1)

freethway
freethway

Reputation: 26

Your code is async which doesn't work with for loop.

for (var x = 0; x < doc.subDoc.length; x++) {
    Doc.findById(form.subDoc[x], function(err, subDoc) {
        populate(subDoc);
    });
}
num_proc = num_proc + 1;
if (num_proc == Doc.subDoc.length) {
    res.json(array);
    array.length = 0;
}

res.json(array) will be executed before populate(subDoc)

You should either use async library or promises to handle this.

Also mongoose already has a built in method to populate sub documents.


****Update****

It seems you'll benefit from this module: deep-populate

Combined with promises here's how I'd re-write your code:

First, I'd promisify the mongoose object (using bluebird)

// Promisification
var Promise = require('bluebird');
var mongoose = require('mongoose');
Promise.promisifyAll(mongoose);

Then in the schema definition I'd use that deep-populate plugin

var deepPopulate = require('mongoose-deep-populate');
Doc.plugin(deepPopulate, options);

So now your route would look like this:

app.get('/docs/:doc/subDocTree', function(req, res) {
    var id = req.params.doc;

    Doc.findById(id).deepPopulate('subDoc').execAsync()
    .then(function(doc){
        res.json(doc.subDoc);
    }).catch(function(err){
        // handle error
        res.send('Something went wrong...' + err.message);
    });
});

Upvotes: 1

Related Questions