4

The matching element looks like that:

{
    "_id": {
        "$oid": "519ebd1cef1fce06f90e3157"
    },
    "from": "Tester2",
    "to": "Tester",
    "messages": [
        {
            "username": "Tester2",
            "message": "heeey",
            "read": false
        },
        {
            "username": "Tester",
            "message": "hi!",
            "read": false
        },
        {
            "username": "Tester2",
            "message": "test",
            "read": false
        }
    ],
}

Now I try to set the read property to the current date just of the subelements where the username is not equal to Tester:

var messages = db.collection('messages');
messages.update(
{
    _id: new BSON.ObjectID("519ebd1cef1fce06f90e3157"),
    messages: {
        $elemMatch: { username: { $ne: "Tester" } }
    }
},
{ $set: { 'messages.$.read': new Date() } },
{ multi: true }, function(error, result) {
    console.log(error);
    console.log(result);
});

But just the first messages subelement read property updates:

{
    "_id": {
        "$oid": "519ebd1cef1fce06f90e3157"
    },
    "from": "Tester2",
    "to": "Tester",
    "messages": [
        {
            "username": "Tester2",
            "message": "heeey",
            "read":  {
                "$date": "2013-01-01T00:00:00.000Z"
            }
        },
        {
            "username": "Tester",
            "message": "hi!",
            "read": false
        },
        {
            "username": "Tester2",
            "message": "test",
            "read": false
        }
    ],
}

What's wrong with the code? I'm using node.js v0.10.8 and MongoDB v2.4.3 together with node-mongodb-native.

David
  • 560
  • 2
  • 10
  • 26

4 Answers4

15

There's nothing wrong with your code; the $ operator contains the index of the first matching array element. The {multi: true} option only makes the update apply to multiple documents, not array elements. To update multiple array elements in a single update you must specify them by numeric index.

So you'd have to do something like this:

messages.update(    
    {
        _id: new BSON.ObjectID("519ebd1cef1fce06f90e3157")
    },
    { $set: { 
        'messages.0.read': new Date(),
        'messages.2.read': new Date()
    } },
    function (err, result) { ... }
);
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • Okay and if I don't know the index before? Couldn't I not simply use `messages.$.read`? – David May 25 '13 at 14:37
  • 1
    You'd need to do a separate query to get all the messages so you could determine the indexes (obviously a total pain). `$` will be 0 (the index of the first matched element), so it will only reference the first element. In short, MongoDB doesn't really support what you're trying to do. – JohnnyHK May 25 '13 at 14:43
  • sounds like your schema is not a good choice for your use case then. – Asya Kamsky May 26 '13 at 04:45
1

This is similar to question: How to Update Multiple Array Elements in mongodb

var set = {}, i, l;
for(i=0,l=messages.length;i<l;i++) {
  if(messages[i].username != 'tester') {
    set['messages.' + i + '.read'] = new Date();
  }
}

.update(objId, {$set:set});
Community
  • 1
  • 1
Zaheer
  • 2,794
  • 5
  • 28
  • 33
1

I know it's a little bit late to answer this question but if you use the $[] operator, it will do the trick.

var messages = db.collection('messages');
messages.update(
{
    _id: new BSON.ObjectID("519ebd1cef1fce06f90e3157"),
    messages: {
        $elemMatch: { username: { $ne: "Tester" } }
    }
},
{ $set: { 'messages.$[].read': new Date() } },
{ multi: true }, function(error, result) {
    console.log(error);
    console.log(result);
});

Dragos Petcu
  • 119
  • 1
  • 2
0

I think ArrayFilters can be used in this case

tim
  • 303
  • 4
  • 14