Reputation: 8411
I want to update a document contains object in MongoDB. Since the updated object is given from the user, I don't know what exactly he was about to change.
For example I have this document in users collection:
{
_id: 1,
date: new Date(),
info: {
first_name: "a",
last_name: "b",
phone: "c"
}
}
The user is able to edit first_name and last_name but not phone.
This is the req.body
(the input I receive from the user):
{
info: {
first_name: "aa"
}
}
Now in order to update only "info.first_name" and preserve the other fields that are stored in the info ojbect, I have to set an update query like this:
db.users.update({}, {$set: {"info.first_name": req.body.info.first_name}}, cb);
But the input is can be dynamic and changeable. I don't know if it's always be first name that was updated it can be last name or even other sub object like address: "info.address.city" (for example)
For that reason I handle those updates like this:
$set: req.body
And that is it. It works but I have an issue with this. It replaces the whole info ojbect and doesn't preserve other keys that were stored in it - phone is getting deleted and last name is getting deleted.
Is there a way to tell MongoDB to not replace the whole object but just update those keys that are mentioned?
Upvotes: 7
Views: 10391
Reputation: 1069
Using $or
and $ne
, you can indeed create one update operation that:
Here's the basic idea:
const new_info = {
first_name: "aa"
}
db.collection.updateOne(
{
_id : ObjectId("1"),
$or: [
{ info.first_name: { $ne: new_info.first_name }},
{ info.last_name: { $ne: new_info.last_name }},
{ info.phone: { $ne: new_info.phone }},
]
},
{
$set: {
info.first_name: new_info.first_name,
info.last_name: new_info.last_name,
info.phone: new_info.phone,
}
}
)
You'd likely have to modify the above to only check for the other fields if they're present in the req.body
. I'm also not certain if querying the nested documents like that is exactly right. However, I hope that gives you the general idea.
All credit goes to Prasad_Saya, who shared this exact approach on this MongoDB forum post
Upvotes: 0
Reputation: 51
would it not be easier to read the current entry in the DB and spread the req.body over the current entry?
// read current entry in database
let currentObject = await user.findOne({_id});
// merge current entry with changed updated key:values
let newObject = ( currentObject, ...req.body );
// write update to database
let user.findOneAndUpdate({_id}, {$set: newObject}, )
Upvotes: 4
Reputation: 184
I also looked into it, and you can't set mongo to only update specific fields. What you can do instead is dynamically build your query based on input.
I usually looped through the input, and then validated what was allowed to be modified, and built a dynamic query based on that:
var updateQuery = {};
for(key of req.body.info) {
if (req.body.info.hasOwnProperty(key) && canBeModified(key)) //this would be a function to validate what can be updated
updateQuery['info.' + key] = req.body.info[key]};
}
db.users.update({}, {$set: updateQuery}, cb);
Upvotes: 0