2

I face an issue when trying to update a single key inside a MongoDB sub-document using Sails.js and the Waterline ORM. This is what my person.js model looks like:

module.exports = {
    attributes: {
        name: { type: 'string', required: true },
        favorites: {
            type: 'json',
            defaultsTo: {
                color: null,
                place: null,
                season: null
            }
        }
    }
}

Now, let's say I want to update a specific person's name and favorite season from my controller, I am doing this:

Person.update({ id: '1' }, {
    name: 'Dan',
    favorites: {
        season: 'Summer'
    }
}).exec(function(error, updatedPerson) {

    console.log(updatedPerson);
});

When I run this, the favorites object gets entirely replaced to just have the one key that I updated (the season key) instead of retaining the other two keys (color and place) while updating just the one I want. What am I missing? How do I get it to only update the keys that I specify?

JackH
  • 4,613
  • 4
  • 36
  • 61

2 Answers2

1

You can use the .native() method on your model that has direct access to the mongo driver and then the $set operator to update the fields independently. However, you need to first convert the object to a one-level document which has the dot notation like

{
    "name": "Dan",
    "favorites.season": "Summer"
}

so that you can use that as:

var criteria = { "id": "1" },
    update = { "$set": { "name": "Dan", "favorites.season": "Summer" } },
    options = { "new": true };

// Grab an instance of the mongo-driver
Person.native(function(err, collection) {        
    if (err) return res.serverError(err);

    // Execute any query that works with the mongo js driver
    collection.findAndModify(
        criteria, 
        null,
        update,
        options,
        function (err, updatedPerson) {
            console.log(updatedPerson);
        }
    );
});

To convert the raw object that needs to be updated, use the following function

var convertNestedObjectToDotNotation = function(obj){
    var res = {};
    (function recurse(obj, current) {
        for(var key in obj) {
            var value = obj[key];
            var newKey = (current ? current + "." + key : key);  // joined key with dot
            if  (value && typeof value === "object") {
                recurse(value, newKey);  // it's a nested object, so do it again
            } else {
                res[newKey] = value;  // it's not an object, so set the property
            }
        }
    })(obj);

    return res;
}

which you can then call in your update as

var criteria = { "id": "1" },
    update = { "$set": convertNestedObjectToDotNotation(params) },
    options = { "new": true };

Check the demo below.

var example = {
 "name" : "Dan",
 "favorites" : {
  "season" : "winter"
 }
};

var convertNestedObjectToDotNotation = function(obj){
 var res = {};
 (function recurse(obj, current) {
  for(var key in obj) {
   var value = obj[key];
   var newKey = (current ? current + "." + key : key);  // joined key with dot
   if (value && typeof value === "object") {
    recurse(value, newKey);  // it's a nested object, so do it again
   } else {
    res[newKey] = value;  // it's not an object, so set the property
   }
  }
 })(obj);
 
 return res;
}


var update = { "$set": convertNestedObjectToDotNotation(example) };

pre.innerHTML = "update =  " + JSON.stringify(update, null, 4);
<pre id="pre"></pre>
chridam
  • 100,957
  • 23
  • 236
  • 235
  • 1
    Worked great! Although I should add that I had to refer to http://stackoverflow.com/a/25701157/2674003 because the native query `{ "id": 1 }` does not work automatically since MongoDB uses ObjectId for IDs. – JackH Nov 22 '16 at 17:55
0

Just alter your query with:

Person.update({ id: '1' },{ name: 'Dan',favorites: { $set: {season: Summer'}}}})
Vaibhav Patil
  • 2,603
  • 4
  • 14
  • 22
  • The issue is, the parameters that need to be updated are sent by the client application that is consuming our API. We can't force users of our API to send it with $set etc. They send us a raw JSON object. Can it still be done? – JackH Nov 22 '16 at 12:18
  • Check for all nested object that will came to update then use $set on that object. – Vaibhav Patil Nov 22 '16 at 12:30