mongoose recursiva poblar

He estado buscando por un tiempo y no encontré ninguna buena respuesta. Tengo un árbol n-deep que estoy almacenando en DB y me gustaría rellenar a todos los padres, así que al final obtengo el árbol completo

node -parent -parent . . -parent 

Hasta ahora llego al nivel 2, y como mencioné necesito llegar al nivel n .

 Node.find().populate('parent').exec(function (err, items) { if (!err) { Node.populate(items, {path: 'parent.parent'}, function (err, data) { return res.send(data); }); } else { res.statusCode = code; return res.send(err.message); } }); 

Simplemente no 🙂

No hay una buena manera de hacerlo. Incluso si hace un poco de reducción de mapas, tendrá un rendimiento terrible y problemas de fragmentación si lo tiene o lo necesitará alguna vez.

Mongo como base de datos NoSQL es realmente genial para almacenar documentos de árbol. Puede almacenar todo el árbol y luego usar map-reduce para obtener algunas hojas en particular si no tiene muchas consultas de “encontrar hojas en particular”. Si esto no funciona para usted, vaya con dos colecciones:

  1. Estructura de árbol simplificada: {_id: "tree1", tree: {1: [2, {3: [4, {5: 6}, 7]}]}} . Los números son solo identificadores de nodos. De esta manera obtendrá todo el documento en una consulta. Luego simplemente extrae todos los identificadores y ejecuta la segunda consulta.

  2. Nodos: {_id: 1, data: "something"} , {_id: 2, data: "something else"} .

Luego puede escribir una función recurrente simple que reemplazará los identificadores de nodo de la primera recostackción con los datos de la segunda. 2 consultas y procesamiento simple del lado del cliente.

Pequeña actualización:

Puedes ampliar la segunda colección para que sea un poco más flexible:

{_id: 2, data: "something", children:[3, 7], parents: [1, 12, 13]}

De esta manera podrás comenzar tu búsqueda desde cualquier hoja. Y luego, use map-reduce para llegar a la parte superior o inferior de esta parte del árbol.

puede hacerlo ahora (con 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 Promise mongoose.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 

alternativa simple, sin 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 }); 

Otro enfoque es aprovechar el hecho de que Model.populate() devuelve una promesa, y que usted puede cumplir una promesa con otra promesa.

Puede rellenar recursivamente el nodo en cuestión a través de:

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

populateParents podría parecerse a lo siguiente:

 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); }); } 

No es el enfoque más eficaz, pero si su N es pequeña, esto funcionaría.

Ahora con Mongoose 4 esto se puede hacer. Ahora puedes repetir más profundo que un solo nivel.

Ejemplo

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

Puede encontrar la documentación oficial de Mongoose Deep Populate aquí .

Intenté la solución de @fzembow pero pareció devolver el objeto desde la ruta más profunda poblada. En mi caso, necesitaba rellenar recursivamente un objeto, pero luego devolver el mismo objeto. Lo hice así:

 // Schema definition const NodeSchema = new Schema({ name: { type: String, unique: true, required: true }, parent: { type: Schema.Types.ObjectId, ref: 'Node' }, }); const Node = mongoose.model('Node', NodeSchema); // method const Promise = require('bluebird'); const recursivelyPopulatePath = (entry, path) => { if (entry[path]) { return Node.findById(entry[path]) .then((foundPath) => { return recursivelyPopulatePath(foundPath, path) .then((populatedFoundPath) => { entry[path] = populatedFoundPath; return Promise.resolve(entry); }); }); } return Promise.resolve(entry); }; //sample usage Node.findOne({ name: 'someName' }) .then((category) => { if (category) { recursivelyPopulatePath(category, 'parent') .then((populatedNode) => { // ^^^^^^^^^^^^^^^^^ here is your object but populated recursively }); } else { ... } }) 

Cuidado, no es muy eficiente. Si necesita ejecutar dicha consulta a menudo o en niveles profundos, debe repensar su diseño