Mongoose populate embedded
Asked Answered
D

4

32

I use Mongoose.js and cannot solve problem with 3 level hierarchy document.

There 2 ways to do it.

First - without refs.

C = new Schema({
    'title': String,
});

B = new Schema({
    'title': String,
    'c': [C]
});

A = new Schema({
    'title': String,
    'b': [B]
});

I need to show C record. How can i populate / find it, knowing only _id of C?

I was try use:

A.findOne({'b.c._id': req.params.c_id}, function(err, a){
    console.log(a);
});

But i dont know how to get from returnet a object only c object that i need.

Second if working with refs:

C = new Schema({
    'title': String,
});

B = new Schema({
    'title': String,
    'c': [{ type: Schema.Types.ObjectId, ref: 'C' }]
});

A = new Schema({
    'title': String,
    'b': [{ type: Schema.Types.ObjectId, ref: 'B' }]
});

How to populate all B, C records to get hierarchy?

I was try to use something like this:

A
.find({})
.populate('b')
.populate('b.c')
.exec(function(err, a){
    a.forEach(function(single_a){
        console.log('- ' + single_a.title);
        single_a.b.forEach(function(single_b){
            console.log('-- ' + single_b.title);
            single_b.c.forEach(function(single_c){
                console.log('--- ' + single_c.title);
            });
        });
    });
});

But it will return undefined for single_c.title. I there way to populate it?

Thanks.

Dunstable answered 25/10, 2012 at 21:30 Comment(1)
Would be good to choose a new accepted answer now that this is supported.Tedmund
S
32

In Mongoose 4 you can populate documents across multiple levels:

Say you have a User schema which keeps track of the user's friends.

var userSchema = new Schema({
  name: String,
  friends: [{ type: ObjectId, ref: 'User' }]
});

Firstly populate() lets you get a list of user friends. But what if you also wanted a user's friends of friends? In that case, you can specify a populate option to tell mongoose to populate the friends array of all the user's friends:

User.
  findOne({ name: 'Val' }).
  populate({
    path: 'friends',
    // Get friends of friends - populate the 'friends' array for every friend
    populate: { path: 'friends' }
  });

Taken from: http://mongoosejs.com/docs/populate.html#deep-populate

Stonewall answered 31/10, 2012 at 17:44 Comment(6)
Same answer from aaron from similar question https://mcmap.net/q/453834/-querying-nested-embedded-documents-with-mongoose/728287Jarrad
FYI - this has been resolved in version 3.6 - github.com/LearnBoost/mongoose/wiki/3.6-Release-NotesCockcroft
@Cockcroft could you provide an example? (the case with refs)Beersheba
@Beersheba - I added an answer as an example of how I did itCockcroft
i'll delete my answer as soon as @Dunstable accepts the correct answer.Jarrad
Not make sense down upvoted this answer because that is invalid right now! @GianPaJ update your answer, not delete.Plasticize
C
44

As of Mongoose 3.6 the ability to recursively populate related documents in a query has been added. Here is an example of how you might do it:

 UserList.findById(listId)
         .populate('refUserListItems')
         .exec(function(err, doc){
             UserListItem.populate(doc.refUserListItems, {path:'refSuggestion'},
                   function(err, data){
                        console.log("User List data: %j", doc);
                        cb(null, doc);
                   }
             );     
          });           

In this case, I am populating an array of id's in 'refUserListItems' with their referenced documents. The result of the query then gets passed into another populate query that references the field of the original populated document that I want to also populate - 'refSuggestion'.

Note the second (internal) populate - this is where the magic happens. You can continue to nest these populates and tack on more and more documents until you have built your graph the way you need it.

It takes a little time to digest how this is working, but if you work through it, it makes sense.

Cockcroft answered 14/8, 2013 at 15:11 Comment(6)
this works, but deep populate given by Buu Nguyen above, is more convenient. No need to do nested loops..Teri
How can I do this using promises?Theall
@Theall - Which part? You could wrap the whole thing in a function that returns a promise and resolve the promise in the callback to .populate. It looks like .populate also returns a promise so you could capture this promise on either of the .populate functions and then call .then on it when you wanted to run it.Cockcroft
@Cockcroft I decided to return the promise that .populate() returns in a chain of .then() functions, and finally run it with the last .then(). I think this looks cleaner than nesting n level of populations. https://mcmap.net/q/453833/-nested-mongoose-populate-with-promisesTheall
What if I have to find docs and not a single document, should I iterate over each of them to populate the inner path or there is a way to do it on all of themDenisse
Why don't you need exec the second time @CockcroftIceland
K
35

in Mongoose 4 you can populate multilevel like this (even in different database or instance)

A
.find({})
.populate({
  path: 'b', 
  model: 'B',
  populate: {
    path: 'c',
    model: 'C'
  }
})
.exec(function(err, a){});
Kelantan answered 23/12, 2015 at 22:22 Comment(4)
This should definitely be marked as the correct answer +1Carmencita
A small deviation.. what happens if B = new Schema({ 'title': String, 'c': [{ type: Schema.Types.ObjectId, ref: 'C' }], 'cc': [{ type: Schema.Types.ObjectId, ref: 'C' }] }); How do I populate both c and cc ?Wace
You can do .populate('c').populate('cc') in chain populate like that. Is that what you want?Kelantan
Or even B.populate([{path: 'c'}, {path: 'cc'}])Kelantan
S
32

In Mongoose 4 you can populate documents across multiple levels:

Say you have a User schema which keeps track of the user's friends.

var userSchema = new Schema({
  name: String,
  friends: [{ type: ObjectId, ref: 'User' }]
});

Firstly populate() lets you get a list of user friends. But what if you also wanted a user's friends of friends? In that case, you can specify a populate option to tell mongoose to populate the friends array of all the user's friends:

User.
  findOne({ name: 'Val' }).
  populate({
    path: 'friends',
    // Get friends of friends - populate the 'friends' array for every friend
    populate: { path: 'friends' }
  });

Taken from: http://mongoosejs.com/docs/populate.html#deep-populate

Stonewall answered 31/10, 2012 at 17:44 Comment(6)
Same answer from aaron from similar question https://mcmap.net/q/453834/-querying-nested-embedded-documents-with-mongoose/728287Jarrad
FYI - this has been resolved in version 3.6 - github.com/LearnBoost/mongoose/wiki/3.6-Release-NotesCockcroft
@Cockcroft could you provide an example? (the case with refs)Beersheba
@Beersheba - I added an answer as an example of how I did itCockcroft
i'll delete my answer as soon as @Dunstable accepts the correct answer.Jarrad
Not make sense down upvoted this answer because that is invalid right now! @GianPaJ update your answer, not delete.Plasticize
O
5

I'm late to this, but I wrote a Mongoose plugin that makes it extremely simple to perform deep model population. For your example, you can do this to populate b and c:

A.find({}, function (err, docs) {
  A.deepPopulate(docs, 'b.c', cb)
}

You can also specify Mongoose populate options for each of the populated paths, like this:

A.deepPopulate(docs, 'b.c', {
  b: { 
    select: 'name'
  }
}, cb)

Check out the plugin documentation for more information.

Odie answered 10/12, 2014 at 19:44 Comment(2)
This looks really cool. I might use it in production when it becomes more mature.Theall
Isn't the b: ment to be a c: ?Bloch

© 2022 - 2024 — McMap. All rights reserved.