24

Assuming the following 3 models:

var CarSchema = new Schema({
  name: {type: String},
  partIds: [{type: Schema.Types.ObjectId, ref: 'Part'}],
});

var PartSchema = new Schema({
  name: {type: String},
  otherIds: [{type: Schema.Types.ObjectId, ref: 'Other'}],
});

var OtherSchema = new Schema({
  name: {type: String}
});

When I query for Cars I can populate the parts:

Car.find().populate('partIds').exec(function(err, cars) {
  // list of cars with partIds populated
});

Is there a way in mongoose to populate the otherIds in the nested parts objects for all the cars.

Car.find().populate('partIds').exec(function(err, cars) {
  // list of cars with partIds populated
  // Try an populate nested
  Part.populate(cars, {path: 'partIds.otherIds'}, function(err, cars) {
    // This does not populate all the otherIds within each part for each car
  });
});

I can probably iterate over each car and try to populate:

Car.find().populate('partIds').exec(function(err, cars) {
  // list of cars with partIds populated

  // Iterate all cars
  cars.forEach(function(car) {
     Part.populate(car, {path: 'partIds.otherIds'}, function(err, cars) {
       // This does not populate all the otherIds within each part for each car
     });
  });
});

Problem there is that I have to use a lib like async to make the populate call for each and wait until all are done and then return.

Possible to do without looping over all cars?

lostintranslation
  • 23,756
  • 50
  • 159
  • 262

4 Answers4

45

Update: Please see Trinh Hoang Nhu's answer for a more compact version that was added in Mongoose 4. Summarized below:

Car
  .find()
  .populate({
    path: 'partIds',
    model: 'Part',
    populate: {
      path: 'otherIds',
      model: 'Other'
    }
  })

Mongoose 3 and below:

Car
  .find()
  .populate('partIds')
  .exec(function(err, docs) {
    if(err) return callback(err);
    Car.populate(docs, {
      path: 'partIds.otherIds',
      model: 'Other'
    },
    function(err, cars) {
      if(err) return callback(err);
      console.log(cars); // This object should now be populated accordingly.
    });
  });

For nested populations like this, you have to tell mongoose the Schema you want to populate from.

Community
  • 1
  • 1
Sven
  • 5,155
  • 29
  • 53
  • Crap RTFM ;) I see that now. However no matter what I try I get back an empty array for otherIds. As a sanity check If I don't call the populate method there is an array of id's (which is correct). Populate is not populating those ids. Running 3.8.22 lastest from npm – lostintranslation Jan 27 '15 at 22:24
  • Also have logging turned on. I do not see another call to the other collection to populate the 'join'. So mongoose isn't even making the call. – lostintranslation Jan 27 '15 at 22:41
  • 2
    @lostintranslation It should be `model: 'Other'` in that nested `populate` call. – JohnnyHK Jan 28 '15 at 02:50
  • @JohnnyHK to the rescue again, thanks! As a side note I though I might be able to get it all the first call Car.find().populate('partIds').populate({path: 'partIds.otherIds', model: 'Other').exec ... No luck guess you have to make the first query call to populate the partIds first. – lostintranslation Jan 28 '15 at 03:07
  • Or, you can try the [mongoose-deep-populate plugin](https://github.com/buunguyen/mongoose-deep-populate) and rewrite the entire thing with: `Car.find().deepPopulate('partIds.otherIds').exec(...)`. (Disclaimer: I'm the author.) – Buu Mar 07 '15 at 18:24
  • The first example worked awesomely for me. Does the model property take anything other than a string, or can you pass an schema reference? – mrClean Sep 04 '17 at 14:18
29

Mongoose 4 support this

Car
.find()
.populate({
  path: 'partIds',
  model: 'Part',
  populate: {
    path: 'otherIds',
    model: 'Other'
  }
})
James
  • 13,571
  • 6
  • 61
  • 83
  • 2
    Note: model: 'Other' is necessary. Without it, it gives empty array. Docs doesn't specify it : http://mongoosejs.com/docs/populate.html#deep-populate – Harshad Ranganathan Dec 25 '15 at 19:46
  • But I want to do double populate in one nested level. So basically something like below, Car .find() .populate({ path: 'partIds', model: 'Part', populate: { path: 'otherIds', model: 'Other' } populate: { path: 'ModelIds', model: 'Model' } }) – Shivam Verma Jul 15 '20 at 08:36
4

Use mongoose deepPopulate plugin

car.find().deepPopulate('partIds.otherIds').exec();
joeytwiddle
  • 29,306
  • 13
  • 121
  • 110
Riyas TK
  • 1,231
  • 6
  • 18
  • 32
0

It's should be better with

Car
.find()
.populate({
    path: 'partIds.othersId'
})