-->

How to update property in multiple objects in an a

2019-05-10 02:28发布

问题:

My question is almost a duplicate of this question. The difference is that I am using minimongo within the Meteor framework/platform. Given this document:

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

How do I query for a particular user and update all the objects in the events array ONLY where 'handled.visible':false to 'handled.visible':true? As much as possible, I would like to have it in a single query. My objective is really to improve the performance of my app. Instead of having to fetch the entire array of objects, process them on the client side (change the object properties) then re-update on the server, it would be great to directly update via Mongo. Changing the data directly on the server is also natively reactive and is advantageous, though is not really necessary for my app.

I am not exactly sure as how to formulate the query in minimongo.

I have tried:

Meteor.users.update({_id: 's8Ppj4ZFSeq9X6xC4', 'events.handled.visible': false }, { $set:{'events.$.handled.visible':true} });

This worked for only the first object found in the array. However I would want to update all objects in the array where the handled.visible is false.

回答1:

You could create a server method that uses the aggregation framework to return a count that you can use in your client as a loop to update each matching array element. The following untested example may have some pointers on how you can go about it (I haven't tested it so feedback on your implementation is much welcome to make this a better solution):

Server:

Add the meteorhacks:aggregate package to your app with

meteor add meteorhacks:aggregate

and then use as

Meteor.methods({
    getInvisibleEventsCount: function(userId){
        var pipeline = [
            { "$match": { "_id": userId, "events.handled.visible": false } },
            { "$unwind": "$events.handled" },
            { "$match": { "events.handled.visible": false } },
            { "$group": {
                "_id": null,
                "count": { "$sum": 1 }
            }}
        ];
        var result = Meteor.users.aggregate(pipeline);
        return result;
    }
});

Client:

Meteor.call("getInvisibleEventsCount", userId, function(err, response) {
    var max = response[0].count;
    while(max--) {
        Meteor.users.update(
            { "_id": userId, "events.handled.visible": false }, 
            { "$set": { "events.$.handled.visible": true} }
        );      
    }
});


回答2:

Meteor currently does not allow to update multiple elements in an array. One has to loop through the array to update for each document returned.



回答3:

I have tried 3 methods so far, one as posted by chridam which works.

Chridam's Method

Method 1 just processes it in the client but selectively chooses only false entries:

testLocalAggregate : function(){
var findQuery = Meteor.users.findOne(Meteor.userId(), {fields:{'events':1}}).events;
var tempArr = [];

_.each(findQuery, function(event, index){
  if(events.handled.visible === false){
    tempArr.push(index);
  }
});


if(tempArr.length){

  var count = tempArr.length;

  while(count --){
    Meteor.users.update(
      { "_id": Meteor.userId(), "events.handled.visible": false },
      {$set:{'events.$.handled.visible':true}}
    );
  }
}

}

Method 2 is by replacing all entries regardless whether true or false, which is the simplest and most straightforward (I know I have specified in my question to only update false entries but for the sake of performance, this solution is also presented).

testUpdate : function(){
var events = Meteor.users.findOne(Meteor.userId(), {fields:{'events':1}}).events;

_.each(events, function(event, index){
  if(events.handled.visible === false){
    events.handled.visible = true;
  }
});

Meteor.users.update({_id : Meteor.userId()}, {$set: {'events': events}});

}

As per testing in Kadira with 5 method calls, the average throughputs and response times are as follows:

Method 1:
Average throughput - .08/min
Average response time - 219ms

Method 2:
Average throughput - .12/min
Average response time - 109ms

Chridam's Method:
Average throughput - .08/min
Average response time - 221ms

Method 1 and Chridam's are almost the same. But I noticed that the UI updates are faster using method 2, with still comparable throughput and response time. I'm just not sure if method 2 will be better as more method calls are done.

Any comments to improve the posted methods or which may be faster in other circumstances will be very much appreciated.