Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Mongoose to update subdocument in array

There are a few posts about this (here and here, for example), but none of them use a native Mongoose method for this. (The first one uses $set, the second one uses the extend npm package. Seems like there should be a "native mongoose" way to do this though.

Schema:

var blogPostSchema = new mongoose.Schema({
    title: String,
    comments: [{
        body: String
    }]
});

Here's what I tried originally:

BlogPost.findById(req.params.postId, function (err, post) {
    var subDoc = post.comments.id(req.params.commentId);
    subDoc = req.body;
    post.save(function (err) {
        if (err) return res.status(500).send(err);
        res.send(post);
    });
});

Problem is this line: subDoc = req.body isn't actually changing the parent doc's subDoc, but rather is just passed by reference. Nothing actually changes in the database after a save() call.

The extend package fixes this by merging the two objects together, as shown in the second SO post linked above (and again here). But isn't there a way to get around this using some Mongoose API method? I can't find anything in the documentation about it (only how to add new subdocs and remove subdocs).

An alternative I wrote was this:

for (var key in subDoc) {
    subDoc[key] = req.body[key] || subDoc[key];
}

which also works, but I wasn't sure if there was anything dangerous about doing it this way. Maybe this is the best way?

Thanks in advance!

like image 585
bobbyz Avatar asked Dec 18 '22 11:12

bobbyz


1 Answers

The reason subDoc = req.body doesn't work is that it is replacing the reference that was created by the first assignment to subDoc with a brand new reference to req.body (assuming it is an object and not a string). This has nothing to do with Mongoose but just how references work in general.

var objA = { a: 1, b: 2};
var objB = { a: 'A', b: 'B'};

var objC = objA;
console.log('Assigning ref to objC from objA', {objA, objB, objC});
// It should console.log the following:
// Assigning ref to objC from objA {
//   "objA": {
//     /**id:2**/
//     "a": 1,
//     "b": 2
//   },
//   "objB": {
//     "a": "A",
//     "b": "B"
//   },
//   "objC": /**ref:2**/
// }
//

objC = objB;            
console.log('Replacing ref to objC with objB', {objA, objB, objC});
// It should console.log the following:
// Replacing ref to objC with objB {
//   "objA": {
//     "a": 1,
//     "b": 2
//   },
//   "objB": {
//     /**id:3**/
//     "a": "A",
//     "b": "B"
//   },
//   "objC": /**ref:3**/
// }

You can use the Document.#set method. Just provide it an object (in this case req.body) that mirrors the document's structure with the values you want to set.

BlogPost.findById(req.params.postId, function(err, post) {
  var subDoc = post.comments.id(req.params.commentId);
  subDoc.set(req.body);

  // Using a promise rather than a callback
  post.save().then(function(savedPost) {
    res.send(savedPost);
  }).catch(function(err) {
    res.status(500).send(err);
  });
});
like image 138
Jason Cust Avatar answered Jan 09 '23 00:01

Jason Cust