简体   繁体   中英

If value of a property is null when updating then that property should not be added to the record

Suppose I have a mongoose schema like this:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var testSchema = new Schema({
    name: {type: String, required: true},
    nickName: {type: String}
});

var Test = module.exports = mongoose.model('Test', testSchema);

I declare methods for CRUD operation using variable Test. From that one such method is update, which is defined as follows:

module.exports.updateTest = function(updatedValues, callback) {

    console.log(updatedValues); //this output is shown below

    Test.update(
        { "_id": updatedValues.id },
        { "$set" : { "name" : updatedValues.name, "nickName" : updatedValues.nickName } },
        { multi: false },
        callback
    );

};

Now, I use this method inside my node router as follows:

router.put('/:id', function(req, res, next) {

    var id = req.params.id,
    var name = req.body.name,
    var nickName = req.body.nickName

    req.checkBody("name", "Name is required").notEmpty();

    var errors = req.validationErrors();

    if(errors) { ........ }
    else {

        var testToUpdate = new Test({
            _id: id,
            name: name,
            nickName: nickName || undefined
        });

        Test.updateTest(testToUpdate, function(err, result) {
            if(err) { throw(err); }
            else { res.status(200).json({"success": "Test updated successfully"}); }
        });

    }
});

Now if I save a new record in database and then see it in database then it looks like:

{
    "_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), //some automatically generated id 
    "name" : "firstTest",
    "__v" : 0
}

Now if I update the same document without changing anything and then if I take a look at same record in database, then I get:

{
    "_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), //some automatically generated id 
    "name" : "firstTest",
    "__v" : 0,
    "nickName" : null
}

Can you see that nickName is set to null? I don't want it to work like this. I want that if my property is null, then that property should not be included in the record.

If you remember, I have console logged the updatedValues before updating it. (see the second code block in question). So, here is the logged values:

{
    "_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), //some automatically generated id 
    "name" : "firstTest"
}

I don't know why, but nickName is not present in the logged values and then after update I get nickName: null . I think, the problem lies in second Code block. Can you please check it?

Note:

Actually I have lot more fields in my schema than I specified in question. Some fields are reference to other records as well.

I wouldn't write this this way, but I'll tell you why your code is failing.

The problem is your $set block

you're choosing to specifically set the value to the update object passed in. If the value is undefined you're forcing mongo to set that to null.

Here's the problem

example, in DB:

{ "_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), "name" : "firstTest", "nickname": "jack", "__v" : 0 }

IF you pass in testToUpdate = { name: 'foo' } you'll end up with Test.update({ ... }, { $set: { name: 'foo', nickname: undefined }}

because you're getting updatedValues.nickname off of the arguments and thats not defined

what you WANT is Test.update({ ... }, { $set: updatedValues } which is translates to Test.update({ ... }, { $set: { name: 'foo' } } .

You're no longer providing a key for nickname, thus not making it set to undefined/null.


I would use a mongoose plugin and not worry about manually passing the fields all the way to your model

see https://github.com/autolotto/mongoose-model-update

you can define the update-able fields and then you can just do

model.update(req.body) and not worry about all this

Even if you don't want to use the plugin you can still just do

Test.findByIdAndUpdate(id, { name, nickname }, callback)

You can prevent such documents from updating in MongoDB by setting the runValidators option to true in the update method.

Ex:

  module.exports.updateTest = function(updatedValues, callback) {

    Test.update(
      { "_id": updatedValues.id },
      { "$set" : { 
        "name" : updatedValues.name, "nickName" : updatedValues.nickName } ,
      },
      { multi: false, runValidators: true },
      callback
    );
  };

In addition, you can also set the option omitUndefined to true to prevent undefined values from being reflected.

Ex:

  module.exports.updateTest = function(updatedValues, callback) {

    Test.update(
      { "_id": updatedValues.id },
      { "$set" : { 
        "name" : updatedValues.name, "nickName" : updatedValues.nickName } ,
      },
      { multi: false, runValidators: true, omitUndefined: true },
      callback
    );
  };

Its true that the problem is your $set part as pointed in the other answers, but using if condition is not the best way to do this. What if there are multiple fields you might need multiple if conditions or even a separate function to deal with this.

Mongoose offers a really good option to deal with this: {omitUndefined: 1} , it will not update all the undefined values in your query.

Taking a look at your code, there is a problem in your update method that won't help you to obtain the result you want.

This is you update code:

module.exports.updateTest = function(updatedValues, callback) {

    console.log(updatedValues); //this output is shown below

    Test.update(
        { "_id": updatedValues.id },
        { "$set" : { "name" : updatedValues.name, "nickName" : updatedValues.nickName } },
        { multi: false },
        callback
    );

};

The problem is in the $set part.

In mongodb you cannot unset a field in a document by just assigning undefined to it. You should use the $unset operator.

So your update code should be something like:

module.exports.updateTest = function(updatedValues, callback) {

    console.log(updatedValues); //this output is shown below
    const operators = {$set: {name: updatedValues.name}};
    if (updatedValues.nickName) {
        operators.$set.nickName = updatedValues.nickName;
    } else {
        operators.$unset = {nickName: 1};
    }
    Test.update(
        { "_id": updatedValues.id },
        operators,
        { multi: false },
        callback
    );

};

Note that the use of $unset is fundamental to remove the field if it already exists in your document.

As you're using $set in the query which says in the documentation

The $set operator replaces the value of a field with the specified value.

So you're forcing to set the undefined value which is null here to the field. Either you set required : true against the nickName field in the schema or try passing JS object instead of writing raw query like, Instead of :

Test.update(
        { "_id": updatedValues.id },
        { "$set" : { "name" : updatedValues.name, "nickName" : updatedValues.nickName } },
        { multi: false },
        callback
    );

Try doing:

var data = {  name : updatedValues.name };
if(updatedValues.nickName){
     data.nickName = updatedValues.nickName;
}
Model.update({ "_id": updatedValues.id }, data ,options, callback)

Check this link in Mongoose documentation for more information on the approach.

The problem is that you are still adding the nickname property on the document - you're just setting it to undefined , but contrary to what you thought that's not the same as not setting it at all.

What you want is to not include the nickname property at all if it's undefined, which you can do like this:

module.exports.updateTest = function(updatedValues, callback) {

    const set = { "name": updatedValues.name };

    if (updatedValues.nickName) {
        set["nickName"] = updatedValues.nickName;
    }

    Test.update({ "_id": updatedValues.id }, { "$set": set}, { multi: false },
        callback
    );

};

我发现的最简单的方法是使用lodash.pickby您只需传递body (或任何与此相关的对象)并删除所有undefinednull字段和键

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM