Rename a subdocument in an array

Given the document below, how to rename 'techId1' to 'techId'. I tried different ways and can't make it work.

{ "_id" : ObjectId("55840f49e0b"), "__v" : 0, "accessCard" : "123456789", "checkouts" : [ { "user" : ObjectId("5571e7619f"), "_id" : ObjectId("55840f49e0bf"), "date" : ISODate("2015-06-19T12:45:52.339Z"), "techId1" : ObjectId("553d9cbcaf") }, { "user" : ObjectId("5571e7619f15"), "_id" : ObjectId("55880e8ee0bf"), "date" : ISODate("2015-06-22T13:01:51.672Z"), "techId1" : ObjectId("55b7db39989") } ], "created" : ISODate("2015-06-19T12:47:05.422Z"), "date" : ISODate("2015-06-19T12:45:52.339Z"), "location" : ObjectId("55743c8ddbda"), "model" : "model1", "order" : ObjectId("55840f49e0bf"), "rid" : "987654321", "serialNumber" : "AHSJSHSKSK", "user" : ObjectId("5571e7619f1"), "techId" : ObjectId("55b7db399") } 

In mongo console I tried, which gives me fine, but nothing updates.

 collection.update({"checkouts._id":ObjectId("55840f49e0b")},{ $rename: { "techId1": "techId" } }); 

I also tried this, which gives me an error. "cannot use part (checkouts.outchId1) to move an item"

 collection.update({"checkouts._id":ObjectId("55856609e0b")},{ $rename: { "checkouts.techId1": "checkouts.techId" } }) 

In mongoose I tried the following.

 collection.findByIdAndUpdate(id, { $rename: { "checkouts.techId1": "checkouts.techId" } }, function (err, data) {}); 

and

 collection.update({'checkouts._id': n1._id}, { $rename: { "checkouts.$.techId1": "checkouts.$.techId" } }, function (err, data) {}); 

Thanks in advance.

+5
source share
1 answer

You were close at the end, but there are a few missing things. You cannot $rename when using the positioning operator, instead you need $set new name and $unset old. But there is one more limitation here, since both of them will belong to the "checkout" as the parent path, since you cannot simultaneously perform both actions.

Another key line in your question is “cross element”, and this is the only thing you cannot do when updating “all” elements of an array at once. Well, not safe and not rewriting new data, anyway.

What you need to do is "iterate" each document and a similar iteration of each member of the array for a "safe" update. You cannot really iterate over only a document and "save" the entire array with the changes. Of course, not when anything else is actively using the data.

I would personally run such an operation in the MongoDB shell, if possible, since this is a “one-way” (hopefully) thing, and it saves the overhead of writing other API code. We also use the Bulk Operations API to make this as efficient as possible. With a mongoose, a little more digging is required to implement, but still can be done. But here is a list of shells:

 var bulk = db.collection.initializeOrderedBulkOp(), count = 0; db.collection.find({ "checkouts.techId1": { "$exists": true } }).forEach(function(doc) { doc.checkouts.forEach(function(checkout) { if ( checkout.hasOwnProperty("techId1") ) { bulk.find({ "_id": doc._id, "checkouts._id": checkout._id }).updateOne({ "$set": { "checkouts.$.techId": checkout.techId1 } }); bulk.find({ "_id": doc._id, "checkouts._id": checkout._id }).updateOne({ "$unset": { "checkouts.$.techId1": 1 } }); count += 2; if ( count % 500 == 0 ) { bulk.execute(); bulk = db.collection.initializeOrderedBulkOp(); } } }); }); if ( count % 500 !== 0 ) bulk.execute(); 

Since the operations of $set and $unset are performed in pairs, we save the total batch size up to 1000 operations to execute in order to save memory usage on the client.

The loop simply looks for documents in which the field to be renamed "exists", and then iterates over each element of the array of each document and makes two changes. As bulk operations, they are not sent to the server until .execute() is .execute() , which also returns one response for each call. This saves a lot of traffic.

If you insist on coding with mongoose. Keep in mind that a .collection acessor is required to access Bulk API methods from the main driver, for example:

 var bulk = Model.collection.inititializeOrderedBulkOp(); 

And the only thing that is sent to the server is the .execute() method, so this is your only execution callback:

 bulk.exectute(function(err,response) { // code body and async iterator callback here }); 

And use .forEach() instead asynchronous flow control like async.each .

Also, if you do this, then keep in mind that as a raw driver method that is not controlled by mongoose, you are not getting the same understanding of the database connection, as is the case with mongoose methods. If you do not know for sure that the database connection has already been established, then safter puts this code in the event callback to connect to the server:

 mongoose.connection.on("connect",function(err) { // body of code }); 

But otherwise, these are the only real ones (besides the call syntax) that you really need.

+7
source

All Articles