24

I have this really annoying issue where i can't update anything using mongoose. It's really frustrating to use, and the documentation is not helping at all.

I have this schema:

var userSchema = mongoose.Schema({

    local            : {
        email        : String,
        password     : String,
    },
    devices : [{
      id : String,
      name : String
    }]
});

And this is the code where i want to add a device to the array devices :

function updateDeviceList(user, deviceID, deviceName)
{
  User.update({ 'local.email' : user}, 
  { $set: {'devices.id' : deviceID, 'devices.name' : deviceName}}, 
  function(err, response)
  {
    if(err)
    {
      console.log("Update device error", err);
    }
    else {
      console.log("Update device OK");
    }
  });
}

At this point i get the error: errmsg: 'cannot use the part (devices of devices.id) to traverse the element ({devices: []})' }

I didn't manage to find an explanation to why this is happening. I have to mention that the document (there is pretty much only one document in the database), is this one:

{
    "_id": {
        "$oid": "5585a196fe11b21100635c74"
    },
    "devices": [],
    "local": {
        "password": "$2a$10$7hXVHw7izcYlqbD6xe/te.0w2zucZ7lA007g9kXdoIMPhZhRyCIru",
        "email": "a@a.a"
    },
    "__v": 0
}
Edeph
  • 732
  • 2
  • 12
  • 29
  • 1
    I am aware of this answer http://stackoverflow.com/questions/24853855/mongoose-update-deep-arrays but i don't think it's ok to come up all the times with this kind of workarounds when mongoose comes with it's own way of doing stuff. – Edeph Jun 20 '15 at 17:31

2 Answers2

28

Try to use the positional $ operator in the update which identifies an element in an array to update without explicitly specifying the position of the element in the array, but this will only ever match one element at a time:

 User.update(
    { 
        "local.email": user,
        "devices.id": { "$ne": deviceID },
        "devices.name": { "$ne": deviceName }
    }, 
    { 
        "$set": { 
            "devices.$.id": deviceID,
            "devices.$.name": deviceName 
        }
    } 
);

From the docs, the positional $ operator acts as a placeholder for the first element that matches the query document, and the array field must appear as part of the query document hence the query document

"devices.id": { "$ne": deviceID },
"devices.name": { "$ne": deviceName }

contains the device array and will match those documents where the device array id is not equal to deviceID and the name is not the same as the name which you are trying to update. This will even match documents where the device array is empty.

chridam
  • 100,957
  • 23
  • 236
  • 235
  • What does "$ne" stand for ? – Edeph Jun 20 '15 at 17:44
  • Also it should be clear that initially i don't have any element in the array, so if i query as you wrote, no result will come up, since there is no `devices.id` – Edeph Jun 20 '15 at 17:45
  • 1
    @Edeph I've updated my answer with some explanations on what's behind the scenes. – chridam Jun 20 '15 at 17:56
  • I modified the code as provided, and now i get: 'The positional operator did not find the match needed from the query. Unexpanded update: devices.$.id' } – Edeph Jun 20 '15 at 17:57
  • I am still super interested in this method if you can help me get it running. I don't understand why i can't use the positional operator. – Edeph Jun 20 '15 at 18:35
  • @Edeph Have you tried using the update option `{ "upsert": true }`? – chridam Jun 20 '15 at 18:40
  • Yes but it didn't changed anything. I might as well try again, just in case. – Edeph Jun 20 '15 at 18:41
  • 2
    @Edeph Since you mentioned initially the device array will be empty, the **`$push`** operation will work but for subsequent updates you might want to consider the [**`findOneAndUpdate()`**](http://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate) method with the **`$set`** operator to update a specific device name or id, rather than an upsert. – chridam Jun 20 '15 at 18:51
  • 1
    The `$push` operation works well for adding devices to my array, and since i only add or remove them, i guess i don't need to positional operator. Thank you! – Edeph Jun 20 '15 at 19:19
  • @Edeph No worries, glad you got the hang of it. – chridam Jun 20 '15 at 19:22
  • findOneAndUpdate doesn't seem to work at all, nor does the find work with `'devices.id' : deviceID`. I really don't understand what i am doing wrong – Edeph Jun 21 '15 at 09:29
  • @chridam How will you update all the embeded documents not just matching ones? – node_saini Sep 26 '16 at 12:02
  • @shubhamsaini Can you please create a new question for this? – chridam Sep 26 '16 at 12:04
  • @chridam [similar question](http://stackoverflow.com/q/4669178/4394926) update all the array elements which matches the criteria!! Is the accepted answer correct and valid? If yes then no point in creating a new question. – node_saini Sep 26 '16 at 12:10
  • @shubhamsaini The answer given by Blakes Seven is the correct approach. – chridam Sep 26 '16 at 12:11
10

Use the $push or other array update operators to add elements to an array. For details, refer http://docs.mongodb.org/manual/reference/operator/update/push/#up._S_push

Sasikanth Bharadwaj
  • 1,457
  • 1
  • 11
  • 11