0

I have a collection that has a structure that looks like the following

{ "_id" : "MHBk8q96vpuRYrAdn", 
    "circles" : { 
        "guests" : 3, 
        "properties" : [      
            {       
                "position" : {  "x" : 146, "y" : 70.5207970},  
                "name" : "circle-1" 
            },
            {       
                "position" : {  "x" : 200, "y" : 85},  
                "name" : "circle-2" 
            }  
        ], 
        "tables" : 1 
    } 
}

I need to be able to either update the position of circles.properties.position if it exists by name, or add a new entry if it does not. For example, update the position of "circle-1" since it exists, but add a new array item for "circle-3" with a name and position. Is it possible to achieve this? So far I have only been able to push onto the array using $push, and I have messed around with the $(query) operator with no success. Thanks.

landland
  • 2,117
  • 3
  • 20
  • 26

1 Answers1

2

Since MongoDB doesn't support upserts to arrays it can be tricky. You can try something like below:

var query = {};
new_circle = { "position" : {  "x" : -1, "y" : -1}, "name" : "circle-1" };

db.foo.find(query).forEach(
    function(doc) {

        // Find index of 'circle-1'
        i = doc.circles.properties.map(
            function(el) { if (el.name == 'circle-1') return 1; else return -1;}
        ).indexOf(1);

        // Update if circle-1 in circles-properties 
        if (i != -1) {
            doc.circles.properties[i] = new_circle;
        }

        // If not push new
        else {
            doc.circles.properties.push(new_circle);
        }

        db.foo.save(doc);
    }
)

Edit

If you cannot use save and update with upsert option replacing if-else block posted above with something like this should do the trick:

if (i != -1) {
    db.foo.update(
        {"_id" : doc._id, "circles.properties.name": "circle-1"},
        {$set: {"circles.properties.$.position": new_circle.position}}
}

else {
    db.foo.update(
        {"_id" : doc._id},
        {$push: {"properties": new_circle }}
    )
}
zero323
  • 322,348
  • 103
  • 959
  • 935
  • ah rats, the framework I use, Meteor, doesn't appear to support collection save at the moment https://github.com/meteor/meteor/issues/584 Thanks for the answer I will upvote and come back to this another time. – landland Nov 03 '13 at 21:22
  • `save` is only a shell shortcut and if you check implementation it can be easily replaced by `db.foo.update({ _id : doc._id }, doc, true}`. Just remember that executing arbitrary js code is not very efficient solution. If it is a common operation and you care about performance it may be better to modify schema so you can use upserts. – zero323 Nov 03 '13 at 21:29
  • The version of Meteor I currently use doesn't support the true flag for update, either. And you are right, this was something I was hoping I could avoid, but thankfully it is not a common operation so it should suffice. Thanks! – landland Nov 03 '13 at 21:33