Search code examples
node.jsmongodbmongoosedocuments

Mongo sub document population


Hello I have three models

Posts:

var PostSchema = new Schema({
title: {
    type: String,
    required: true,
    default: '',
},
content: {
    type: String,
    required: true,
    default: '',
},
community: {
    type: String,
    default: '',
},
price: {
    type: String,
    required: true,
    default: '',
},
location: {
    type: String,
    required: true,
    default: '',
},
images: {
    type:[],
    required: true,
},
user: {
    type: Schema.ObjectId,
    required: true,
    ref: 'User',
},
accepted : {
    type: [],
},
comments: {
    type: [Schema.Types.ObjectId],
    ref:"Comment"
}
});

Users:

var userSchema = mongoose.Schema({

local            : {
    email        : String,       
    password     : String,
},

firstName: { type: String, required: true },
lastName: { type: String, required: true },
location: { type: String, required: true },
profilepicture: { type: String },

});

Comments

var CommentSchema = new Schema({
title: {
    type: String,
    default: '',
},
content: {
    type: String,
    default: '',
},
user: { 
    type: Schema.ObjectId, 
    ref: 'User' 
},
post: {
    type: Schema.ObjectId,
    ref: 'Post'
},
offer: {
    type: String,
    default: '',
}
});

I am trying to allow the Posts model to have an object id array of comments and for the comments to have there user objectid populated. I am using the below code for my posts route and I can get all the comments to populate but I cant get the user of the comment to populate.In theory this should work according to the mongo docs, but its not. Any ideas?

app.get('/api/posts', function(req, res) {

Post.find().populate('user comments comments.user').exec(function(err, posts) {
    if (err) {
        res.render('error', {
            status: 500
        });
    } else {
        res.jsonp(posts);
    }
});
});

Solution

  • To do what you want, you would need to call populate on the inner "comments" object after the initial populate call has bee done. In a simplified version of your listing:

    var async = require("async"),
        mongoose = require("mongoose"),
        Schema = mongoose.Schema;
    
    mongoose.connect("mongodb://localhost/user");
    
    var postSchema = new Schema({
      title: { type: String, required: true, default: '' },
      user: { type: Schema.Types.ObjectId, required: true, ref: 'User' },
      comments: [{ type: Schema.Types.ObjectId, ref: "Comment" }]
    });
    
    var userSchema = new Schema({
      name: String
    });
    
    var commentSchema = new Schema({
      content: String,
      user: { type: Schema.Types.ObjectId, ref: "User" },
      post: { type: Schema.Types.ObjectId, ref: "Post" }
    });
    
    var Post = mongoose.model( "Post", postSchema );
    var User = mongoose.model( "User", userSchema );
    var Comment = mongoose.model( "Comment", commentSchema );
    

    Populating the "user" on the comments would be like this:

    Post.find().populate("user comments").exec(function(err,docs) {
    
        async.forEach(docs,function(doc,callback) {
          Comment.populate( doc.comments,{ "path": "user" },function(err,output) {
            //console.log( "doc: " + doc );
            //console.log( "proc: " + output );
            callback();
          });
    
        },function(err) {
          console.log( "all: " + JSON.stringify(docs,undefined,4 ));
        });
    
    });
    

    Or however you are actually processing the results from your find on the post. The point is that your "comments" needs to be populated first and then you can call .populate(), using the the model form, on the whole "comments" array per document in the result.