raoel
raoel

Reputation: 320

MongoDB update object and remove properties?

I have been searching for hours, but I cannot find anything about this.

Situation: Backend, existing of NodeJS + Express + Mongoose (+ MongoDB ofcourse).

  1. Frontend retrieves object from the Backend.
  2. Frontend makes some changes (adds/updates/removes some attributes).

Now I use mongoose: PersonModel.findByIdAndUpdate(id, updatedPersonObject);

Result: added properties are added. Updated properties are updated. Removed properties... are still there!

Now I've been searching for an elegant way to solve this, but the best I could come up with is something like:

var properties = Object.keys(PersonModel.schema.paths);
for (var i = 0, len = properties.length; i < len; i++) {
     // explicitly remove values that are not in the update
     var property = properties[i];
     if (typeof(updatedPersonObject[property]) === 'undefined') {
         // Mongoose does not like it if I remove the _id property
         if (property !== '_id') {
             oldPersonDocument[property] = undefined;
         }
     }
}
oldPersonDocument.save(function() {
    PersonModel.findByIdAndUpdate(id, updatedPersonObject);
});

(I did not even include trivial code to fetch the old document).

I have to write this for every Object I want to update. I find it hard to believe that this is the best way to handle this. Any suggestions anyone?

Edit: Another workaround I found: to unset a value in MongoDB you have to set it to undefined. If I set this value in the frontend, it is lost in the REST-call. So I set it to null in the frontend, and then in the backend I convert all null-values to undefined.

Still ugly though. There must be a better way.

Upvotes: 4

Views: 1868

Answers (2)

John Doe
John Doe

Reputation: 301

Also really struggling with this but I don't think your solution is too bad. Our setup is frontend -> update function backend -> sanitize users input -> save in db. For the sanitization part, we use a helper function where we integrate your approach.

private static patchModel(dbDocToUpdate: IModel, dataFromUser: Record<string, any>): IModel {
    const sanitized = {};
    const properties = Object.keys(PersonModel.schema.paths);
    for (const key of properties) {
      if (key in dbDocToUpdate) {
        sanitized[key] = data[key];
      }
    }

    Object.assign(dbDocToUpdate, sanitized);

    return dbDocToUpdate;
  }

That works smoothly and sets the values to undefined. Hence, they get removed from the document in the db.

The only problem that remains for us is that we wanted to allow partial updates. With that solution that's not possible and you always have to send everything to the backend.

EDIT Another workaround we found is setting the property to an empty string in the frontend. Mongo then also removes the property in the database

Upvotes: 0

mmmm
mmmm

Reputation: 639

You could use replaceOne() if you want to know how many documents matched your filter condition and how many were changed (I believe it only changes one document, so this may not be useful to know). Docs: https://mongoosejs.com/docs/api/model.html#model_Model.replaceOne

Or you could use findOneAndReplace if you want to see the document. I don't know if it is the old doc or the new doc that is passed to the callback; the docs say Finds a matching document, replaces it with the provided doc, and passes the returned doc to the callback., but you could test that on your own. Docs: https://mongoosejs.com/docs/api.html#model_Model.findOneAndReplace So, instead of:

PersonModel.findByIdAndUpdate(id, updatedPersonObject);, you could do:

PersonModel.replaceOne({ _id: id }, updatedPersonObject);

As long as you have all the properties you want on the object you will use to replace the old doc, you should be good to go.

Upvotes: 1

Related Questions