2

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);
};
JohnSnow
  • 6,911
  • 8
  • 23
  • 44

2 Answers2

2

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!

Ankit Gomkale
  • 704
  • 8
  • 16
  • Probably good idea to further split it down by having a schema for post. – Mikey Apr 15 '17 at 08:45
  • I understand, but I initially wanted to do this project using just one Schema (probably cause I'm crazy) is there any way to do this with just the one Schema? – JohnSnow Apr 15 '17 at 09:02
  • Yes, it's possible to manage it in one single schema. In that case, you will have to run a for loop and match `_id` of each object in `comments` array with the `req.body.commentID`. That nasty, but you will be ok, provided the length of the array is small. – Ankit Gomkale Apr 15 '17 at 09:51
  • @AnkitGomkale I followed your advice and everything seemed to be working until I ran into a big problem. Here is what I ended up doing, and this works however for some reason it only updates the comment of the first POST only. I think there is something I don't understand with regards to `doc.save()`. Kindly have a look above, I have updated it. – JohnSnow Apr 16 '17 at 02:19
  • Maybe because you are doing it for only first post: `let thisComment = doc.posts[0].comments.filter((comment) => { return comment._id == req.body.commentID; })`. How are you even sure that comment you are looking for is in `post[0]`? I think you need to loop every post for every comment: `forEach(post) { forEach(comment){ if(comment id match){ update comment} }}`. Once loop is finished, you can save the document. Or nested forEach() may cause async issue and you might end up saving doc before nested forEach() completes. In that case, save doc in each loop. Told you it's gonna be nasty :D ` – Ankit Gomkale Apr 16 '17 at 05:17
  • @AnkitGomkale actually the doc.posts.length will always be equal to 1. Because of the projection It will always get the correct post, so I will only have to loop through the comments... Do you see what I mean? – JohnSnow Apr 16 '17 at 05:26
  • @AnkitGomkale `thisComment[0].content` is always the right comment in the right post, but it is the fact that when I `dov.save()` after it that it only updates the first post for some weird reason :S – JohnSnow Apr 16 '17 at 05:35
  • @JohnSnow I am little confused by your comment. What do you mean by "it only updates the first post"? There is anyway only 1 post in each user schema, right? Please show your user document or give example. – Ankit Gomkale Apr 17 '17 at 06:09
-3
controller.editComment = (req, res, next) => {
    User.findOneAndUpdate(
        { id: req.query.userid, 'posts._id': req.params.postid, 'comments._id':req.body.commentID },
        {
            $push: {
                'posts.$.comments': {  content: req.body.edited },
            }
        }
    )
        .exec()
        .then(() => {
            res.send('deleted');
        })
        .catch(next);
};