2

I have an object assigned to a variable, such as:

var child = {
    { 
        _id: 'ObjectID("16e7f7baa05c3a9d751363")',
        title: 'Testing',
        sort: 3,
        active: true 
    }

The following does not work, and honestly I am unsure of what I'm missing:

db.category.update(
    { _id: "thisistheparentdocumentsid", "children._id": child._id },
    {
        $set: {
            "children.$.title": child.title,
            "children.$.active": child.active,
            "children.$.sort": child.sort
        }
    }
);

What I really want to do, as I want to reuse this method and not repeat myself later is:

db.category.update(
    { _id: "thisistheparentdocumentsid", "children._id": child._id },
    {
        $set: {
            "children" : child
        }
    }
);

That doesn't work either. I referenced this post: Update array element by id with mongo query

As well as: http://docs.mongodb.org/manual/reference/operator/update/positional/

But my query is not working. Am I missing something elementary here?

--- Edit ---

Here is the result when I query based on the parent document

db.category.findOne({ "_id" : "9dYgKdfFczgiRcNouij"});
{
        "title" : "ParentTest",
        "active" : true,
        "children" : [
                {
                        "_id" : ObjectId("680d55c6995ef6f0748278c2"),
                        "title" : "ChildTest",
                        "active" : true
                                            },
                {
                        "_id" : ObjectId("2b4469c1a4c8e086942a1233"),
                        "title" : "ChildTest2"
                        "active" : true
                }
        ],
        "_id" : "9dYgKdfFczgiRcNouij"
}

I'm passing data to the server, such as... Method.call("UpdateCommand", id, child);

The variable id is the parent id of the document, and the child is the object I mentioned.

I use child._id.toString() to generate the matching ObjectId("...");

So more specifically:

db.category.update(
        { _id: id, "children._id": child._id.toString() },
        {
            $set: {
                "children.$.title": child.title,
                "children.$.active": child.active,
                "children.$.sort": child.sort,
                "children.$.uppercase": child.title.toUpperCase()
            }
        }
    );

Yet this doesn't work. I'm thinking something is off with my selector, but I can use findOne() with the same method and it does return the proper document, and using $elemMatch returns the specified child item in the array.

Anything I might be missing? I've ran all the values through console.log to make sure I'm getting them, and I am.

Community
  • 1
  • 1
user1447679
  • 3,076
  • 7
  • 32
  • 69

1 Answers1

3

So what you are saying is that you have a document like this:

{
    "_id": ObjectId("5430b55d214047b8ee55d40b"),
    "children": [
        { 
            "_id": ObjectID("16e7f7baa05c3a9d751363"),
            "title": "Testing",
            "sort": 2,
            "active": false 
        }
    }
}

Or something similar, but at least with an array of children like that. As long as you are happy to update "all" if the elements in the "child" entry with the values in your variable object then there is nothing wrong with this:

db.category.update(
    { "_id": ObjectId("5430b55d214047b8ee55d40b"), "children._id": child._id },
    { "$set": { "children.$": child }
)

And that will replace the entire element in position.

If you are worried about replacing the entire content where you might have something like this:

{
    "_id": ObjectId("5430b55d214047b8ee55d40b"),
    "children": [
        { 
            "_id": ObjectID("16e7f7baa05c3a9d751363"),
            "title": "Testing",
            "sort": 2,
            "active": false,
            "created": ISODate("2014-10-03T03:09:20.609Z")
        }
    }
}

Then you can process the keys of the object to construct your $set statement:

var update = { "$set": {} };

for ( var k in child ) {
    if ( k != "_id" )
        update["$set"]["children.$."+k] = child[k];
}

db.category.update(
    { "_id": ObjectId("5430b55d214047b8ee55d40b"), "children._id": child._id },
    update
);

Which will process the update in place but does not overwrite the "created" field that was not supplied.

Also note your quotes '' around the ObjectId value there, but I'm hoping that is just a typo, otherwise you need to eval() the string to get a valid object.

The single array elements here are just and example. It is the positional $ operator syntax with the matching element in the query that matters here. There is no difference for a single array element or hundreds. Just as long as you are only updating one element of the array.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • I just added more details to the bottom of my question, literally at the same time of your answer. Reading your answer now, but if you would be so kind as to review my update? – user1447679 Oct 05 '14 at 03:17
  • @user1447679 Already saw it. It does not matter how many elements are in the array. My own revision reflects this. And if your parentId is a string (odd that you are mixing types here) then it is a string. – Neil Lunn Oct 05 '14 at 03:19
  • I'm logging the child object prior to sending it via the update method on the server, and it looks like this: Object {_id: "ObjectID("19636533c66b0fd89f72f4b1")", title: "Testing", sort: 2, active: true} Does it seem incorrect for the ObjectID to be wrapped in quotes? I'm wondering if it's being sent as a string. – user1447679 Oct 05 '14 at 03:33
  • @user1447679 Yes it is incorrect to wrap this in quotes because that means it is just a string. Since you actually appear to have "real" ObjectId values in your data you need to process the string with something like `client._id = eval(client._id);` in order to cast this as a real ObjectId value. You should really look into what code is even producing this as a string in the first place. – Neil Lunn Oct 05 '14 at 03:53
  • Thank you again. I marked your answer as correct, as it is. It seems I'm running into some other issues related to the framework I'm using (MeteorJS) and how it currently handles traditional ObjectID's. After attempting your last suggestion, I'm getting ReferenceError: ObjectID is not defined, which led me to this: http://stackoverflow.com/questions/11979403/meteor-collection-update-with-traditional-id I'm going to work on this some more, but that has to be the issue. Thank you... like, a lot, for continuously commenting and your detailed answer. Your details in the answer are VERY helpful – user1447679 Oct 05 '14 at 04:11
  • I should have probably tagged this with "meteor" but I was oblivious to the connection of the problem. Thanks again. – user1447679 Oct 05 '14 at 04:12
  • @user1447679 I noticed this was in fact meteor based when you poste d the main document `_id` values. There are ways to handle types correctly though and even override that default "string" `_id` value. Look at the [EJSON](http://docs.meteor.com/#ejson) section of the meteor documentation for correct type handling. This is based on the [MongoDB extended JSON syntax](http://docs.mongodb.org/manual/reference/mongodb-extended-json/). The main specifics here were about the positional operator though. – Neil Lunn Oct 05 '14 at 04:21
  • Very interesting. I wonder if I should override the default string for _id created by meteor and use new OBjectId... I think this gets in the way of minimongo and some of the features though. – user1447679 Oct 05 '14 at 23:34