6

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!

Community
  • 1
  • 1
bobbyz
  • 4,946
  • 3
  • 31
  • 42

1 Answers1

12

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);
  });
});
Jason Cust
  • 10,743
  • 2
  • 33
  • 45
  • Thanks! I think I just needed to step away from the computer for a minute because I thought of the .set method after a good lunch. I'll try this as soon as I get home. – bobbyz Nov 16 '16 at 23:12
  • @bobbyz Any luck? – Jason Cust Nov 17 '16 at 15:46
  • Haven't tried it yet, but I will when I get in today. Out of curiousity, what is the benefit of using the promise over the callback? I use them in Angular and like them, but is the benefit mostly just for how it looks, or does it perform better? Also, is using document.set the preferred way of handling PUT requests on the parent document too? And will it keep properties I don't include in req.body, acting like a PATCH request? I guess I can just play with all this when I get in but it'd be nice to have another voice in it. – bobbyz Nov 17 '16 at 16:11
  • @bobbyz Lots of questions there... . Using callbacks, promises, generators, etc. is really up to you but you can read [this article for more info](https://medium.com/@rdsubhas/es6-from-callbacks-to-promises-to-generators-87f1c0cd8f2e). I don't know if it is the preferred way but if you have an object vs a value and need to set the object values to your document (such as a `PATCH` operation) then `set` handles this for you cleanly. Hope that helps. – Jason Cust Nov 18 '16 at 17:24
  • Thanks! Everything works great and it all makes total sense. – bobbyz Nov 18 '16 at 18:47
  • `subDoc.set(req.body);` is what I was looking for. Thanks! – Arian Acosta Dec 10 '17 at 02:00