mongoose recursive populate mongoose recursive populate mongodb mongodb

mongoose recursive populate


you can do this now (with https://www.mongodb.com/blog/post/introducing-version-40-mongoose-nodejs-odm)

var mongoose = require('mongoose');// mongoose.Promise = require('bluebird'); // it should work with native Promisemongoose.connect('mongodb://......');var NodeSchema = new mongoose.Schema({    children: [{type: mongoose.Schema.Types.ObjectId, ref: 'Node'}],    name: String});var autoPopulateChildren = function(next) {    this.populate('children');    next();};NodeSchema.pre('findOne', autoPopulateChildren).pre('find', autoPopulateChildren)var Node = mongoose.model('Node', NodeSchema)var root=new Node({name:'1'})var header=new Node({name:'2'})var main=new Node({name:'3'})var foo=new Node({name:'foo'})var bar=new Node({name:'bar'})root.children=[header, main]main.children=[foo, bar]Node.remove({}).then(Promise.all([foo, bar, header, main, root].map(p=>p.save()))).then(_=>Node.findOne({name:'1'})).then(r=>console.log(r.children[1].children[0].name)) // foo

simple alternative, without Mongoose:

function upsert(coll, o){ // takes object returns ids inserted    if (o.children){        return Promise.all(o.children.map(i=>upsert(coll,i)))            .then(children=>Object.assign(o, {children})) // replace the objects children by their mongo ids            .then(o=>coll.insertOne(o))            .then(r=>r.insertedId);    } else {        return coll.insertOne(o)            .then(r=>r.insertedId);    }}var root = {    name: '1',    children: [        {            name: '2'        },        {            name: '3',            children: [                {                    name: 'foo'                },                {                    name: 'bar'                }            ]        }    ]}upsert(mycoll, root)const populateChildren = (coll, _id) => // takes a collection and a document id and returns this document fully nested with its children  coll.findOne({_id})    .then(function(o){      if (!o.children) return o;      return Promise.all(o.children.map(i=>populateChildren(coll,i)))        .then(children=>Object.assign(o, {children}))    });const populateParents = (coll, _id) => // takes a collection and a document id and returns this document fully nested with its parents, that's more what OP wanted  coll.findOne({_id})    .then(function(o){      if (!o.parent) return o;      return populateParents(coll, o.parent))) // o.parent should be an id        .then(parent => Object.assign(o, {parent})) // replace that id with the document    });


Another approach is to take advantage of the fact that Model.populate() returns a promise, and that you can fulfill a promise with another promise.

You can recursively populate the node in question via:

Node.findOne({ "_id": req.params.id }, function(err, node) {  populateParents(node).then(function(){    // Do something with node  });});

populateParents could look like the following:

var Promise = require('bluebird');function populateParents(node) {  return Node.populate(node, { path: "parent" }).then(function(node) {    return node.parent ? populateParents(node.parent) : Promise.fulfill(node);  });}

It's not the most performant approach, but if your N is small this would work.


Now with Mongoose 4 this can be done. Now you can recurse deeper than a single level.

Example

User.findOne({ userId: userId })    .populate({         path: 'enrollments.course',        populate: {            path: 'playlists',            model: 'Playlist',            populate: {                path: 'videos',                model: 'Video'            }        }     })    .populate('degrees')    .exec()

You can find the official documentation for Mongoose Deep Populate from here.