How to populate a sub-document in mongoose after creating it?


Question

I am adding a comment to an item.comments list. I need to get the comment.created_by user data before I output it in the response. How should I do this?

    Item.findById(req.param('itemid'), function(err, item){
        var comment = item.comments.create({
            body: req.body.body
            , created_by: logged_in_user
        });

        item.comments.push(comment);

        item.save(function(err, item){
            res.json({
                status: 'success',
                message: "You have commented on this item",

//how do i populate comment.created_by here???

                comment: item.comments.id(comment._id)
            });
        }); //end item.save
    }); //end item.find

I need to populate the comment.created_by field here in my res.json output:

                comment: item.comments.id(comment._id)

comment.created_by is a user reference in my mongoose CommentSchema. It currently is only giving me a user id, I need it populated with all the user data, except for password and salt fields.

Here is the schema as people have asked:

var CommentSchema = new Schema({
    body          : { type: String, required: true }
  , created_by    : { type: Schema.ObjectId, ref: 'User', index: true }
  , created_at    : { type: Date }
  , updated_at    : { type: Date }
});

var ItemSchema = new Schema({
    name    : { type: String, required: true, trim: true }
  , created_by  : { type: Schema.ObjectId, ref: 'User', index: true }
  , comments  : [CommentSchema]
});
1
55
10/23/2012 8:09:15 PM

Accepted Answer

In order to populate referenced subdocuments, you need to explicitly define the document collection to which the ID references to (like created_by: { type: Schema.Types.ObjectId, ref: 'User' }).

Given this reference is defined and your schema is otherwise well defined as well, you can now just call populate as usual (e.g. populate('comments.created_by'))

Proof of concept code:

// Schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

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

var CommentSchema = new Schema({
  text: String,
  created_by: { type: Schema.Types.ObjectId, ref: 'User' }
});

var ItemSchema = new Schema({
   comments: [CommentSchema]
});

// Connect to DB and instantiate models    
var db = mongoose.connect('enter your database here');
var User = db.model('User', UserSchema);
var Comment = db.model('Comment', CommentSchema);
var Item = db.model('Item', ItemSchema);

// Find and populate
Item.find({}).populate('comments.created_by').exec(function(err, items) {
    console.log(items[0].comments[0].created_by.name);
});

Finally note that populate works only for queries so you need to first pass your item into a query and then call it:

item.save(function(err, item) {
    Item.findOne(item).populate('comments.created_by').exec(function (err, item) {
        res.json({
            status: 'success',
            message: "You have commented on this item",
            comment: item.comments.id(comment._id)
        });
    });
});
71
9/26/2015 4:17:37 PM

This might have changed since the original answer was written, but it looks like you can now use the Models populate function to do this without having to execute an extra findOne. See: http://mongoosejs.com/docs/api.html#model_Model.populate. You'd want to use this inside the save handler just like the findOne is.


Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon