Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose find/update subdocument

I have the following schemas for the document Folder:

var permissionSchema = new Schema({     role: { type: String },     create_folders: { type: Boolean },     create_contents: { type: Boolean } });  var folderSchema = new Schema({     name: { type: string },     permissions: [ permissionSchema ] }); 

So, for each Page I can have many permissions. In my CMS there's a panel where I list all the folders and their permissions. The admin can edit a single permission and save it.

I could easily save the whole Folder document with its permissions array, where only one permission was modified. But I don't want to save all the document (the real schema has much more fields) so I did this:

savePermission: function (folderId, permission, callback) {     Folder.findOne({ _id: folderId }, function (err, data) {         var perm = _.findWhere(data.permissions, { _id: permission._id });                          _.extend(perm, permission);          data.markModified("permissions");         data.save(callback);     }); } 

but the problem is that perm is always undefined! I tried to "statically" fetch the permission in this way:

var perm = data.permissions[0]; 

and it works great, so the problem is that Underscore library is not able to query the permissions array. So I guess that there's a better (and workgin) way to get the subdocument of a fetched document.

Any idea?

P.S.: I solved checking each item in the data.permission array using a "for" loop and checking data.permissions[i]._id == permission._id but I'd like a smarter solution, I know there's one!

like image 692
Darko Romanov Avatar asked Oct 02 '14 08:10

Darko Romanov


People also ask

How do I use Find ID and update?

As the name implies, findOneAndUpdate() finds the first document that matches a given filter , applies an update , and returns the document. By default, findOneAndUpdate() returns the document as it was before update was applied. You should set the new option to true to return the document after update was applied.

What is mongoose subdocument?

In Mongoose, subdocuments are documents that are nested in other documents. You can spot a subdocument when a schema is nested in another schema. Note: MongoDB calls subdocuments embedded documents.

What is __ V in MongoDB?

The __v field is called the version key. It describes the internal revision of a document. This __v field is used to track the revisions of a document. By default, its value is zero ( __v:0 ).


2 Answers

So as you note, the default in mongoose is that when you "embed" data in an array like this you get an _id value for each array entry as part of it's own sub-document properties. You can actually use this value in order to determine the index of the item which you intend to update. The MongoDB way of doing this is the positional $ operator variable, which holds the "matched" position in the array:

Folder.findOneAndUpdate(     { "_id": folderId, "permissions._id": permission._id },     {          "$set": {             "permissions.$": permission         }     },     function(err,doc) {      } ); 

That .findOneAndUpdate() method will return the modified document or otherwise you can just use .update() as a method if you don't need the document returned. The main parts are "matching" the element of the array to update and "identifying" that match with the positional $ as mentioned earlier.

Then of course you are using the $set operator so that only the elements you specify are actually sent "over the wire" to the server. You can take this further with "dot notation" and just specify the elements you actually want to update. As in:

Folder.findOneAndUpdate(     { "_id": folderId, "permissions._id": permission._id },     {          "$set": {             "permissions.$.role": permission.role         }     },     function(err,doc) {      } ); 

So this is the flexibility that MongoDB provides, where you can be very "targeted" in how you actually update a document.

What this does do however is "bypass" any logic you might have built into your "mongoose" schema, such as "validation" or other "pre-save hooks". That is because the "optimal" way is a MongoDB "feature" and how it is designed. Mongoose itself tries to be a "convenience" wrapper over this logic. But if you are prepared to take some control yourself, then the updates can be made in the most optimal way.

So where possible to do so, keep your data "embedded" and don't use referenced models. It allows the atomic update of both "parent" and "child" items in simple updates where you don't need to worry about concurrency. Probably is one of the reasons you should have selected MongoDB in the first place.

like image 52
Neil Lunn Avatar answered Sep 30 '22 17:09

Neil Lunn


In order to validate subdocuments when updating in Mongoose, you have to 'load' it as a Schema object, and then Mongoose will automatically trigger validation and hooks.

const userSchema = new mongoose.Schema({   // ...   addresses: [addressSchema], }); 

If you have an array of subdocuments, you can fetch the desired one with the id() method provided by Mongoose. Then you can update its fields individually, or if you want to update multiple fields at once then use the set() method.

User.findById(userId)   .then((user) => {     const address = user.addresses.id(addressId); // returns a matching subdocument     address.set(req.body); // updates the address while keeping its schema            // address.zipCode = req.body.zipCode; // individual fields can be set directly      return user.save(); // saves document with subdocuments and triggers validation   })   .then((user) => {     res.send({ user });   })   .catch(e => res.status(400).send(e)); 

Note that you don't really need the userId to find the User document, you can get it by searching for the one that has an address subdocument that matches addressId as follows:

User.findOne({   'addresses._id': addressId, }) // .then() ... the same as the example above 

Remember that in MongoDB the subdocument is saved only when the parent document is saved.

Read more on the topic on the official documentation.

like image 24
Arian Acosta Avatar answered Sep 30 '22 18:09

Arian Acosta