Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB. How to update json property inside an array

I'm using the Mean stack. I'm uncomfortably new to this and have got myself in a pickle. I've seen examples of updating records using set or push but because I'm trying to update one json object within a json object within an array of json objects I'm having trouble.

Consider the following Schema

token: {
    type: String,
    required: "FB access token required."
},
fbId: {
    type: String,
    required: "FB id required.",
    unique: true
},
accounts: {
    type: Array,
    default: []
}

My accounts array consists of json objects, each of which look like this:

{
   "name": "some name",
   "credentials": {
       "username": "some username",
       "password": "some password"
}

I'm trying to update accounts each time the user adds a new one. So in this case I can use $push just fine. But I don't want duplicate names, and $push fails here. So I tried using $set, but $set isn't inserting objects with new names into accounts. So I tried using upsert:true with $set, but now I'm getting duplicate errors.

Here is an example where I try to index name in accounts. I'm assuming I'm screwing up my query some how.

var name = "some name";
var fbId = "some fb Id";
var token = "some token";
var username = "some other username";
var password = "some other password";

var query = {
    fbId: fbId,
    token: token,
    "accounts.name":name
};
var update = {
    $set: {
         accounts: [{
            name: name,
            credentials: {
                username: username,
                password: password
            }
        }]
    }

};
var options = {
    upsert: true
};
User.update(query, update, options,
    function (err, numberAffected, rawResponse) {
        //handle it
    });
);

To sum up my question, is it reasonable to think I can update this with one call and if so how? Or should I find the object, replace its values and then reinsert it to the database?

like image 484
zafrani Avatar asked Sep 28 '22 22:09

zafrani


1 Answers

You can use the $ array update operator:

var query = {
    fbId: fbId,
    token: token,
    "accounts.name":name
};
var update = {
    $set: {
         "accounts.$": [{       //The $ operator goes into the update object
            name: name,
            credentials: {
                username: username,
                password: password
            }
        }]
    }
};

According to the documentation:

The positional $ operator identifies an element in an array to update without explicitly specifying the position of the element in the array.

EDIT:

According to this MongoDB JIRA page, using upsert with the $ positional operator is still an open issue.

I searched around and found a possible solution, which unfortunately defeats your aim of achieving this with one call. It can be found here.

To summarize that answer, you will have to first send the update query without the upsert flag. Then check the response to see whether the document was updated, if not (this means such a subdocument does not exist), send another query with the $push operator to insert a new subdocument in the array.

like image 185
ZeMoon Avatar answered Oct 02 '22 13:10

ZeMoon