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.