Search code examples
node.jsmongodbexpressmongoosesubdocument

How to find a sub document in mongoose without using _id fields but using multiple properties


I have a sample schema like this -

Comment.add({
    text:String,
    url:{type:String,unique:true},
    username:String,
    timestamp:{type:Date,default:Date}
});
Feed.add({
    url:{type:String, unique:true },
    username:String,
    message:{type:String,required:'{PATH} is required!'},
    comments:[Comment],
    timestamp:{type:Date,default:Date}
});

Now, I don't want to expose the _id fields to the outside world that's why I am not sending it to the clients anywhere. Now, I have two important properties in my comment schema (username,url) What I want to do is update the content of the sub document that satisfies

  1. feed.url
  2. comment.url
  3. comment.username

if the comment.username is same as my client value req.user.username then update the comment.text property of that record whose url was supplied by client in req.body.url variable.

One long and time consuming approach I thought is to first find the feed with the given url and then iterating over all the subdocuments to find the document which satisfies the comment.url==req.body.url and then check if the comment.username==req.user.username if so, update the comment object. But, I think there must be an easier way of doing this? I already tried -

db.feeds.update({"username":"[email protected]","comments.username":"[email protected]","comments.url":"test"},{$set:{"comments.$.text":"updated text 2"}})

found from http://www.tagwith.com/question_305575_how-to-find-and-update-subdocument-within-array-based-on-parent-property

but this updates even when the comments.url or comments.usernamematches other sub documents

and I also tried

db.feeds.distinct("comments._id",{"comments.url":req.body.url})

to find the _id of document associated with the url but it returns all the _id in the subdocument


Solution

  • First off - you should not rely on _id not being seen by the outside world in terms of security. This is a very bad idea for a multitude of reasons (primarily REST and also the fact that it's returned by default with all your queries).

    Now, to address your question, what you want is the $elemMatch operator. This says that you're looking for something where the specified sub-document within an array matches multiple queries.

    E.g.

    db.feeds.update({
        "username":"[email protected]",
        comments: {
            $elemMatch: {
                username: "[email protected]",
                url: "test"
            }
        }
    }, {$set: {"comments.$.text":"updated text 2"}})
    

    If you don't use $elemMatch you're saying that you're ok with the document if any of the comments match your query - i.e. if there is a comment by user "[email protected]", and separate comment has a url "test", the document will match unless you use $elemMatch