37

I have this setup in my MongoDB

Items:

title: String
comments: [] // of objectId's

Comments:

user: ObjectId()
item: ObjectId()
comment: String

Here's my Mongoose schema:

itemSchema = mongoose.Schema({
    title: String,
    comments: [{ type: Schema.Types.ObjectId, ref: 'comments' }],
});

Item = mongoose.model('items', itemSchema);

commentSchema = mongoose.Schema({
    comment: String,
    user: { type: Schema.Types.ObjectId, ref: 'users' },
});

Comment = mongoose.model('comments', commentSchema);

This is where I get my items along with the comments:

Item.find({}).populate('comments').exec(function(err, data){
    if (err) return handleError(err);
    res.json(data);
});

How do I populate the comments array with it's respective user? Since each comment has a user ObjectId()?

JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
iamjonesy
  • 24,732
  • 40
  • 139
  • 206
  • You can do it in two stages as shown in the accepted answer to this question: http://stackoverflow.com/questions/19222520/populate-nested-array-in-mongoose – JohnnyHK Jun 25 '14 at 17:56

10 Answers10

114

One more way (easier) to do this:

Item
  .find({})
  .populate({
 path:     'comments',   
 populate: { path:  'user',
      model: 'users' }
  })
  .exec(function(err, data){
    if (err) return handleError(err);
    res.json(data);
});
Antenka
  • 1,519
  • 2
  • 19
  • 29
31

As a complete example calling populate on the result objects:

Item.find({}).populate("comments").exec(function(err,data) {
    if (err) return handleError(err);

    async.forEach(data,function(item,callback) {
        User.populate(item.comments,{ "path": "user" },function(err,output) {
            if (err) throw err; // or do something

            callback();
        });
    }, function(err) {
        res.json(data);
    });

});

The call to .populate() in the form invoked from the model takes either a document or an array as it's first argument. So you loop through the returned results for each item and call populate this way on each "comments" array. The "path" tells the function what it is matching.

This is done using the "async" version of forEach so it is non-blocking, but generally after all the manipulation all of the items in the response are not only populated with comments but the comments themselves have the related "user" details.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • @neil Lunn I am getting below error. Can you please help me with that. async.forEach(data,function(item,callback) { ^ ReferenceError: async is not defined – FarheenP Jul 08 '15 at 11:52
  • @FarheenP - install it using `npm install async` and then you can include it by `var async = require('async');`. See https://github.com/caolan/async – petrkotek Aug 04 '15 at 13:01
13

Simpler

Item
  .find({})
  .populate({
    path: 'comments.user',
    model: 'users' }
  })
  .exec(function(err, data){
    if (err) return handleError(err);
    res.json(data);
});
steampowered
  • 11,809
  • 12
  • 78
  • 98
10

To add one final method that people may want to use to select only particular fields from sub-documents, you can use the following 'select' syntax:

  Model.findOne({ _id: 'example' })
    .populate({ 
      path: "comments", // 1st level subdoc (get comments)
      populate: { // 2nd level subdoc (get users in comments)
        path: "user",
        select: 'avatar name _id'// space separated (selected fields only)
      }
    })
    .exec((err, res) => { 
        // etc
     });
jacobedawson
  • 2,929
  • 25
  • 27
10

I use this:

.populate({
            path: 'pathName',
            populate: [
                {
                    path: 'FirstSubPathName',
                    model: 'CorrespondingModel',
                },
                {
                    path: 'OtherSubPathName',
                    model: 'CorrespondingModel',
                },
                {
                    path: 'AnotherSubPathName',
                    model: 'CorrespondingModel',
                },
            ]
        });

it's the more easier way that i find to do this.I expect to help. :)

Neto Araujo
  • 101
  • 1
  • 3
8

To populate sub-sub document and populate from multiple schemas

ProjectMetadata.findOne({id:req.params.prjId})
.populate({
    path:'tasks',
    model:'task_metadata',
    populate:{
        path:'assigned_to',
        model:'users',
        select:'name employee_id -_id' // to select fields and remove _id field

    }
})
.populate({
    path:'client',
    model:'client'
})
.populate({
    path:'prjct_mgr',
    model:'users'
})
.populate({
    path:'acc_exec',
    model:'users'
})
.populate({
    path:'prj_type',
    model:'project_type'
}).then ( // .. your thing

or you can do it in following manner ..

   ProjectMetadata.findOne({id:req.params.prjId})
    .populate(
        [{
        path:'tasks',
        model:TaskMetadata,
        populate:[{
            path:'assigned_to',
            model:User,
            select:'name employee_id'
        },
        {
            path:'priority',
            model:Priority,
            select:'id title'
        }],
        select:"task_name id code assign_to stage priority_id"
    },
    {
        path:'client',
        model:Client,
        select:"client_name"
    },
    {
        path:'prjct_mgr',
        model:User,
        select:"name"
    },
    {
        path:'acc_exec',
        model:User,
        select:'name employee_id'
    },
    {
        path:'poc',
        model:User,
        select:'name employee_id'
    },
    {
        path:'prj_type',
        model:ProjectType,
        select:"type -_id"
    }

])

I found the second method (of using array) more useful when I had to get multiple sub-sub documents of same parent.

Yash Ojha
  • 792
  • 9
  • 17
3

do it try it`s working find Project and get project related populate Task and Perticular Task User find

db.Project.find()
.populate({
    path: 'task',
    populate: { path: 'user_id'}
})
.exec(async(error,results)=>{

})
  • 1
    Welcome to SO! Try to format your answer for better readability, this will help other stumbling on the same issue. – Bert H Jun 28 '19 at 13:17
1

You can also populate subdocument by this in mongoose -

Item.find({}).populate("comments.user")
0

Check the screenshot below. This thing works like charm!!! enter image description here

Al Mobin
  • 19
  • 2
  • 1
    [Please don't post screenshots of text](https://meta.stackoverflow.com/a/285557/354577). They can't be searched or copied and offer poor usability. Instead, paste the code as text directly into your question. If you select it and click the `{}` button or Ctrl+K the code block will be indented by four spaces, which will cause it to be rendered as code. – ChrisGPT was on strike May 14 '20 at 22:06
0

This worked for me:

i.e. no need for model

    .populate({
      path: 'filters',
      populate: {
        path: 'tags',
        populate: {
          path: 'votes.user'
        }
      }
    })
    .populate({
      path: 'members'
    })