Aggregation returing only the first matched key in nested array mongoose

Viewed 110

I have a below mongoose schema:

const PostSchema=mongoose.Schema({
    author: String,
    uid: String,
    comments:[
        {
            commentAuthor: String,
            uid: String,
            comment: String,
            userProfile: String,
            dateOfComment: String,
            replies: [{
                replyAuthor: String,
                authorProfile: String,
                reply: String,
                dateOfReply: String
            }]
        }
    ]
},{collection: 'SocialPost'});

I have fetch all those posts in which a particular user with a given id has commented i.e. all those documents in which comments array contains an object in which uid is equal to the given uid. For ex. suppose the user id is 101, then the result of the query might look like:

[{
 author: 'gratis',
 uid: 101,
 ...,
 comments:[{
  commentAuthor: 'gratis',
  uid: 101,
  ...
},...]
},
...]

To put everything in perspective, I want to fetch all those posts on which a particular user has commented , or even replied, along with the comments and replies of only that user.

The best I could do is this:

SocialPost.aggregate([
{$match:{}},
{$unwind: "comments"},
{'$match': {"comments.uid": req.body.uid}}
])

This query is returning only the first matched item of comments array of respective post. I want all items that match the condition.

Please help me to design the right query for my problem.

Thank You!

2 Answers

If you have to arrays and you want to find a doc which has a value in any on of them you might want to use $or along with $elemMatch.

SocialPost.find({
    "$or": [
         { 'comments': { "$elemMatch" : { 'uid' : yourId} } },
         {'comments.replies':{"$elemMatch":{'uid':yourId}}}
     ]
},(err,result)=>{});

Although your approach is not incorrect per se, writing the comments as an array of objects inside the PostsSchema leads to the kind of problems that you're experiencing now, as running any kind of query involving comments from more than one post gets a little bit too verbose. Personally, my approach to this problem would be creating a CommentsSchema totally separated from the posts' and then link both via Mongoose's populate() method. The new schema could be something like this:

const CommentsSchema = new mongoose.Schema({
  userId: {
    type: Schema.Types.ObjectId,
    required: true,
    ref: Users
  },
  postId: {
    type: Schema.Types.ObjectId,
    required: true,
    ref: Posts
  },
  parentCommentId: {
    type: Schema.Types.ObjectId,
    ref: Comments
  },
  comment: String,
  timestamps: true
});

This way, each comment would include information about the user who created it (assuming you have a UsersSchema and a collection of Users) via userId, the post that it refers to (postId) and, if the comment is an answer to another one, the comment it answers (parentCommentId). In order to extract a post's comments as an extra field in your Posts collection, you could create a virtual field in your PostsSchema:

PostsSchema.virtual('comments', {
  ref: Comments,
  localField: '_id',
  foreignField: 'postId'
});

Then, when you have a post document, you can populate its comments like this: post.populate("comments").execPopulate(); Finally, extracting all the comments written by a user would just be a matter of querying your Comments collection:

const userComments = await Comments.find({ userId: user._id });

I know these are a lot of changes. But hopefully this approach will be much easier to deal with.

Related