Reputation: 3
I would like to create the data for blog post with list of posts, its associated comments with replies along with user details.
I tried nested populate but could do for only one level. Like I get the comments object and in this I need to populate replies and userId. Inside replies I need to populate userId and repliesCount. In this, only the populate that is given at last returns the populated data other fields list only the Ids.
const mongoose = require('mongoose')
var ObjectId = mongoose.Schema.Types.ObjectId
let PostsSchema = new mongoose.Schema({
userId: {
type: ObjectId,
ref: 'User'
},
celebrityId: {
type: ObjectId,
ref: 'CelebrityDetails'
},
type: {
type: Number,
default: 1
},
isForwarded: {
type: Number,
default: 0
},
originalPostId: {
type: ObjectId,
default: null
},
title: {
type: String
},
description: {
type: String
},
status: {
type: Number,
default: 1
}
}, {
timestamps: true,
collection: 'fan_posts'
})
PostsSchema.virtual('isMyPost', {
ref: 'User',
localField: 'userId',
foreignField: '_id',
count: true
})
PostsSchema.virtual('comments', {
ref: 'PostComment',
localField: '_id',
foreignField: 'postId'
})
PostsSchema.virtual('commentCount', {
ref: 'PostComment',
localField: '_id',
foreignField: 'postId',
count: true
})
PostsSchema.set('toObject', { virtuals: true })
PostsSchema.set('toJSON', { virtuals: true })
const Posts = mongoose.model('Posts', PostsSchema)
Posts.consts = {
STATUS_INACTIVE: 0,
STATUS_ACTIVE: 1,
STATUS_DELETED: 2,
IS_FORWARDED: 1,
TYPE_TEXT: 1,
TYPE_IMAGE: 2,
TYPE_VIDEO: 3,
TYPE_ASK_TEXT: 4,
TYPE_ASK_IMAGE: 5,
TYPE_RATING: 6
}
module.exports = Posts
const mongoose = require('mongoose')
var ObjectId = mongoose.Schema.Types.ObjectId
let PostCommentSchema = new mongoose.Schema({
userId: {
type: ObjectId,
ref: 'User'
},
postId: {
type: ObjectId,
ref: 'FanPosts'
},
comment: {
type: String
},
isReply: {
type: Number,
default: 0
},
parentCommentId: {
type: ObjectId,
ref: 'PostComment'
}
}, {
timestamps: true,
collection: 'post_comment'
})
PostCommentSchema.set('toObject', { virtuals: true })
PostCommentSchema.set('toJSON', { virtuals: true })
PostCommentSchema.virtual('replies', {
ref: 'PostComment',
localField: '_id',
foreignField: 'parentCommentId'
})
PostCommentSchema.virtual('repliesCount', {
ref: 'PostComment',
localField: '_id',
foreignField: 'parentCommentId',
count: true,
justOne: true
})
const PostComment = mongoose.model('PostComment', PostCommentSchema)
PostComment.consts = {
TYPE_NOT_REPLY: 0,
TYPE_REPLY: 1
}
module.exports = PostComment
Posts.find({celebrityId: celebrityId, status: Posts.consts.STATUS_ACTIVE})
.populate({ path: 'userId', select: 'fmId fullName' })
.populate({ path: 'isMyPost', match:{_id: userId} })
.populate({ path: 'comments', match: {isReply: PostComment.consts['TYPE_NOT_REPLY']}, populate: {path: 'userId', select: 'fmId fullName'}, populate: {path: 'replies', match: {isReply: PostComment.consts['TYPE_REPLY']}, populate: {path: 'userId', select: 'fmId fullName'} } })
.populate({ path: 'commentCount'})
.exec(function(err, posts){
if (err) return res.send({status: status.codes.http['serverError'], message: err})
return res.send({status: status.codes.http['success'], posts: posts})
})
Result:
{
"status": 200,
"posts": [
{
"type": 1,
"isForwarded": 0,
"originalPostId": null,
"status": 1,
"_id": "5d2b16519788076fafe7700c",
"celebrityId": "5d167ca099a55c2d2494dcf8",
"post": "hi how are you",
"userId": {
"_id": "5d167a397213b127aafb48f3",
"fmId": "FM499KNWDL",
"fullName": "Mohideen Abubucker"
},
"createdAt": "2019-07-14T11:47:29.863Z",
"updatedAt": "2019-07-14T11:47:29.863Z",
"__v": 0,
"isMyPost": 1,
"comments": [
{
"isReply": 0,
"_id": "5d33721a12aba934e6520f2d",
"userId": "5d167a397213b127aafb48f3",
"postId": "5d2b16519788076fafe7700c",
"comment": "comment 1",
"createdAt": "2019-07-20T19:57:14.747Z",
"updatedAt": "2019-07-20T19:57:14.747Z",
"__v": 0,
"replies": [
{
"isReply": 1,
"_id": "5d33724e12aba934e6520f2e",
"userId": {
"_id": "5d167a397213b127aafb48f3",
"fmId": "FM499KNWDL",
"fullName": "Mohideen Abubucker"
},
"postId": "5d2b16519788076fafe7700c",
"comment": "comment 1",
"parentCommentId": "5d33721a12aba934e6520f2d",
"createdAt": "2019-07-20T19:58:06.286Z",
"updatedAt": "2019-07-20T19:58:06.286Z",
"__v": 0,
"id": "5d33724e12aba934e6520f2e"
}
],
"id": "5d33721a12aba934e6520f2d"
}
],
"commentCount": 2,
"id": "5d2b16519788076fafe7700c"
},
]
}
I need to populate userId inside comments object and add repliesCount to the replies object.
The issue is, I can populate only one column. In the query, if you see I would have given populate for both userId and replies. Since replies being the last I am getting the replies data.
I don't know how to populate both replies and userId
{
"status": 200,
"posts": [
{
"type": 1,
"isForwarded": 0,
"originalPostId": null,
"status": 1,
"_id": "5d2b16519788076fafe7700c",
"celebrityId": "5d167ca099a55c2d2494dcf8",
"post": "hi how are you",
"userId": {
"_id": "5d167a397213b127aafb48f3",
"fmId": "FM499KNWDL",
"fullName": "Mohideen Abubucker"
},
"createdAt": "2019-07-14T11:47:29.863Z",
"updatedAt": "2019-07-14T11:47:29.863Z",
"__v": 0,
"isMyPost": 1,
"comments": [
{
"isReply": 0,
"_id": "5d33721a12aba934e6520f2d",
"userId": {
"_id": "5d167a397213b127aafb48f3",
"fmId": "FM499KNWDL",
"fullName": "Mohideen Abubucker"
},
"postId": "5d2b16519788076fafe7700c",
"comment": "comment 1",
"createdAt": "2019-07-20T19:57:14.747Z",
"updatedAt": "2019-07-20T19:57:14.747Z",
"__v": 0,
"replies": [
{
"isReply": 1,
"_id": "5d33724e12aba934e6520f2e",
"userId": {
"_id": "5d167a397213b127aafb48f3",
"fmId": "FM499KNWDL",
"fullName": "Mohideen Abubucker"
},
"postId": "5d2b16519788076fafe7700c",
"comment": "comment 1",
"parentCommentId": "5d33721a12aba934e6520f2d",
"createdAt": "2019-07-20T19:58:06.286Z",
"updatedAt": "2019-07-20T19:58:06.286Z",
"__v": 0,
"id": "5d33724e12aba934e6520f2e",
"repliesCount": 1
}
],
"id": "5d33721a12aba934e6520f2d"
}
],
"commentCount": 2,
"id": "5d2b16519788076fafe7700c"
},
]
}
Upvotes: 0
Views: 554
Reputation: 707
The problem here:
.populate({ path: 'comments', match: {isReply: PostComment.consts['TYPE_NOT_REPLY']}, populate: {path: 'userId', select: 'fmId fullName'}, populate: {path: 'replies', match: {isReply: PostComment.consts['TYPE_REPLY']}, populate: {path: 'userId', select: 'fmId fullName'} } })
Your populate parameters:
{
path: 'comments',
match: {
isReply: PostComment.consts['TYPE_NOT_REPLY']
},
populate: {
path: 'userId',
select: 'fmId fullName'
},
populate: {
path: 'replies',
match: {
isReply: PostComment.consts['TYPE_REPLY']
},
populate: {
path: 'userId',
select: 'fmId fullName'
}
}
}
Object literals are a flavor of key value map thus you cannot have multiple keys of the same value within a single level of the object.
You have 2 keys with the value "populate" at a single level of the object and it looks like only the last one is persisted in the object.
You can see that here: https://jsfiddle.net/32oe6w8y/1/
Check the mongoose documentation, I'm sure they have a mechanism to deal with this (populate may take an array.)
EDT: Based on this you can pass an array into populate: https://stackoverflow.com/a/21100156/2004999
That may be the solution to your problem.
Fixed problematic populate parameters:
{
path: 'comments',
match: {
isReply: PostComment.consts['TYPE_NOT_REPLY']
},
populate: [{
path: 'userId',
select: 'fmId fullName'
},{
path: 'replies',
match: {
isReply: PostComment.consts['TYPE_REPLY']
},
populate: {
path: 'userId',
select: 'fmId fullName'
}
}]
}
Upvotes: 1