2

How to Update Multiple Array objects in mongodb. This question was asked earlier - How to Update Multiple Array elements in mongodb

But that didn't work for me. I have an array of objects

{
    "_id" : ObjectId("4d2d8deff4e6c1d71fc29a07"),
    "user_id" : "714638ba-2e08-2168-2b99-00002f3d43c0",
    "events" : [
    {
        "handled" : {
            "name": "Mike",
            "visibile": false
        },
        "profile" : 10,
        "data" : "....."
    }
    {
        "handled" : {
            "name": "Shaun",
            "visibile": false
        },
        "profile" : 10,
        "data" : "....."
    }
    {
        "handled" : {
            "name": "Glen",
            "visibile": true
        },
        "profile" : 20,
        "data" : "....."
    }
        ...
]
}

And I want to update all the events.handled.visible:false to "events.handled.visible":true.

I tried

collection.aggregate({
      $match: {
        _id: ObjectId("4d2d8deff4e6c1d71fc29a07")
      }
    }, {
      $unwind: "$events"
    }, {
      "$match": {
        "events.handled.visible": false
      }
    }, {
      "$group": {
        "_id": "$_id",
        "count": {
          "$sum": 1
        }
      }
    }, {
      "$group": {
        "_id": null,
        "count": {
          "$max": "$count"
        }
      }
    }, function(err, res) {
      var max = res[0].count;
      while (max--) {
        collection.update({
          "events.handled.visible": 1
        }, {
          "$set": {
            "events.$.handled.visible": true
          }
        }, {
          "multi": true
        }, function(err, res) {
          if (err) {
            console.log("Whoops! " + err)
          } else {
            console.log("Yay! " + res)
          }
        })
      }
    } //End Function
  ) //End Aggregate

But that didn't update anything. What am I missing?

Community
  • 1
  • 1
Naveen Paul
  • 454
  • 8
  • 18
  • 1
    `{ "events.handled.visible": 1}` should be `{ "events.handled.visible": false}`. Not to sure what you thought the additional `$group` stages were supposed to do. As the author of the answer you seem to be following on the linked post, you are not following the rule of *"Don't assume"* and are just trying to update `n` times. It would be better to update until there was no update count returned. That way you are sure everything is `false`. – Blakes Seven Mar 16 '16 at 04:16
  • Also, looping with async methods inside the loop is bad. You should use a loop that only iterates on the callback from the inner `.update()`, that way you are not queueing up operations that are like going to actually execute in parallel, rather than the serial order your operation is expecting. – Blakes Seven Mar 16 '16 at 04:22
  • 1
    why does not the update work when I try to match by multiple conditions? For eg: { "events.handled.visible": false, "events.handled.name": {$ne:null}} did not update anything. – Naveen Paul Mar 16 '16 at 06:14
  • 1
    That would be a different question. You [Ask a new Question](http://stackoverflow.com/questions/ask) when you have more questions. But just read what [`$elemMatch`](https://docs.mongodb.org/manual/reference/operator/query/elemMatch/) is meant to do. And depends where you are using it. That would be "before" `$unwind`, but "after" it does not apply. – Blakes Seven Mar 16 '16 at 06:17

1 Answers1

3

While I don't think that iterating over an expected count is the "best" way to do this, here is basically the corrections to what you are trying to do, with some help by the node async library for flow control:

  async.waterfall(
    [
      function(callback) {
        collection.aggregate(
          [
            { "$match": { "_id": ObjectId("4d2d8deff4e6c1d71fc29a07") } },
            { "$unwind": "$events" },
            { "$match": { "events.handled.visibile": false } },
            { "$group": {
              "_id": "$_id",
              "count": { "$sum": 1 }
            }}
          ],
          callback
        );
      },

      function(results,callback) {
        console.log(results);
        var result = results[0];

        async.whilst(
          function() { return result.count-- },
          function(callback) {
            collection.update(
              { "_id": result._id, "events.handled.visibile": false },
              { "$set": { "events.$.handled.visibile": true } },
              callback
            )
          },
          callback
        );
      }
    ],
    function(err) {
      if (err) throw err;
      // finished now
    }
  );

So the main things here are that your .update() statement should instead be looking for the "events.handled.visibile": false matches, and of course you need to make sure the operations execute "in series", otherwise there is no real guarantee that you are in fact grabbing the document in an altered state from the previous .update().

The async.whilst handles the flow control so that it waits for completion of each .update() until executing the next. When it's first logical statement is true ( counter depleted ) and all .update() statements are run then the loop will release to the final callback.

Where possible you should really be using "Bulk" update operations as referenced in the answer that you are following. That sends all updates and once and only has one response, so the overhead of waiting for each operation to complete is eliminated.

Community
  • 1
  • 1
Blakes Seven
  • 49,422
  • 14
  • 129
  • 135