Search code examples
node.jsmongodbmongoosenested-documents

Updating a double nested array MongoDB


Consider this schema:

let userSchema = new mongoose.Schema({
id: String,
displayName: String,
displayImage: String,
posts: [
    {
        url: String,
        description: String,
        likes: [String],
        comments: [
            { content: String, date: String, author: { id: String, displayName: String, displayImage: String } }
        ]
    }
]
});

I am able to delete a certain item from the comments array using this query

controller.deleteComment = (req, res, next) => {
User.findOneAndUpdate(
    { id: req.query.userid, 'posts._id': req.params.postid, },
    {
        $pull: {
            'posts.$.comments': { _id: req.body.commentID },
        }
    }
)
    .exec()
    .then(() => {
        res.send('deleted');
    })
    .catch(next);
};

Is there anyway that I can UPDATE an element inside the comments array by using the $set operator? I need to changed the contents of a comment based on its comment ID.. something like this:

controller.editComment = (req, res, next) => {
    User.findOneAndUpdate(
        { id: req.query.userid, 'posts._id': req.params.postid, 'comments._id':req.body.commentID },
        {
            $set: {
                'posts.$.comments': {  content: req.body.edited },
            }
        }
    )
        .exec()
        .then(() => {
            res.send('deleted');
        })
        .catch(next);
};

This ^ is obviously not working but I'm wondering if there is a way I can do this?

UPDATE As per suggestions below I am doing the following to manage only one Schema. This works, however only the first post's comments get updated regardless of which posts comments I am editing. I have checked and the return docs are always correct. There must be a problems with the doc.save() method.

controller.editComment = (req, res, next) => {
User.findOne(
    { id: req.query.userid, 'posts._id': req.params.postid },
    { 'posts.$.comments._id': req.body.commentID }
)
    .exec()
    .then((doc) => {
        let thisComment = doc.posts[0].comments.filter((comment) => { return comment._id == req.body.commentID; });
        thisComment[0].content = req.body.edited;
        doc.save((err) => { if (err) throw err; });
        res.send('edited');
    })
    .catch(next);
};

Solution

  • I don't know a simple (or even tough :P) way to achieve what are trying to do. In mongo, manipulation is comparatively tough in doubly nested arrays and hence, best to be avoided.

    If you are still open for schema changes, I would suggest you create a different schema for comments and refer that schema inside user schema.

    So your comment schema will look like this:

    let commentSchema = new mongoose.Schema({
         content: String, 
         date: String,
         author: { 
              id: String,
              displayName: String,
              displayImage: String
         }
    });
    

    And you user schema should look like this:

    let userSchema = new mongoose.Schema({
         id: String,
         displayName: String,
         displayImage: String,
         posts: [{
            url: String,
            description: String,
            likes: [String],
            comments: [{
                type: Schema.Types.ObjectId,
                ref: 'comment'   //reference to comment schema
            }]
        }]
    });
    

    This way your data manipulation will be a lot easier. You can populate comments while fetching user document. And, notice how easy update/delete operations are, given that you already know that _id of the comments you want to update.

    Hope you find this answer helpful!