Default value not set while using Update with Upsert as true
Asked Answered
J

2

8

I have the following model for users:

var UserSchema = new mongoose.Schema({

    name: String,
    dob: Date,
    sex: String,
    photo: String,
    email: {type: String, index: {unique: true, required: true}},
    created: {type: Date, default: Date.now}
});

var User = mongoose.model('Users', UserSchema);

As you can see the 'created' field takes a default value of the current date so that it is automatically set when a new user is created.

I use the following query when user details are posted:

User.findOneAndUpdate({email: user.email}, user, {upsert: true}, function (err, user) {
            if (err) {
                return callback (err);
            } else {
                callback(null, user);
            }
        });

The purpose of using findOneAndUpdate with upsert: true is to either return an existing profile, or create a new one. It also updates any fields based on the data posted.

However, the created field gets updated with the current date each time, even though the created field is not posted. How can I make sure that this field is set only once?

EDIT

An example object from the database:

{
    "_id" : ObjectId("54620b38b431d48bce7cab81"),
    "email" : "[email protected]",
    "__v" : 0,
    "name" : "somone",
    "sex" : "male"
}

It turns out that the created field is not being set even while creating a new object using upsert. Mongoose just returns the current date based on the schema even though it does not exist in the document.

So, the question now becomes: How do I make sure that using upsert creates the default value for a field not supplied in the arguments?

Judicial answered 11/11, 2014 at 12:50 Comment(1)
Can you please write your user object in the question. So that I can try to help you.Tipperary
R
6

findOneAndUpdate simply sends a MongoDB findAndModify request (see findOneAndUpdate). What this means is that it skips all the mongoose magic involved with the schema setters, getters, defaults, etc. Validation is only run on create/save so the way around this is to do a .findOne(), check existence/create a new one, and then .save().

see this issue for more discussion

EDIT:

In regards to the first question about changing the date each time, you could change the schema a bit. Get rid of the default value, and instead add this after declaring the schema:

UserSchema.pre("save", function (next) {
    if (!this.created) {
        this.created = new Date();
    }
    next();
});

That will only create a date if the created: value is not present, and should prevent it from changing the creation date each time (when using .save()).

see Mongoose middleware

Rubrician answered 11/11, 2014 at 15:42 Comment(1)
Thanks a lot @cdbajorin, I have gone with the approach of checking findOne and then saving a new entry if it returns null.Judicial
S
19

For adding defaults to your document if it was created with findOneAndUpdate (it didn't exist before the query) and you did not provide the field in the update you should use setDefaultsOnInsert.

When upsert and setDefaultsOnInsert are both true, the defaults will be set if the record is not found and a new one is created. This skips the workflow of having to check if the record exists and if not then creating a new one with 'save' just to make sure defaults are set.

I have had the same issue (record created with findOneAndUpdate with upsert: true) and the default value for a field was not added to the record, even though it was in the schema. This is only in regards to adding defaults when using findOneAndUpdate to create documents, not for skipping the update of the 'created' field.

e.g.

User.findOneAndUpdate({email: user.email}, user, {upsert: true, setDefaultsOnInsert:true}, ...)
Secretin answered 3/1, 2018 at 9:38 Comment(1)
Please elaborate on your answer. I've stopped it from being NAA. You should now consider improving it. See How to Answer.Argueta
R
6

findOneAndUpdate simply sends a MongoDB findAndModify request (see findOneAndUpdate). What this means is that it skips all the mongoose magic involved with the schema setters, getters, defaults, etc. Validation is only run on create/save so the way around this is to do a .findOne(), check existence/create a new one, and then .save().

see this issue for more discussion

EDIT:

In regards to the first question about changing the date each time, you could change the schema a bit. Get rid of the default value, and instead add this after declaring the schema:

UserSchema.pre("save", function (next) {
    if (!this.created) {
        this.created = new Date();
    }
    next();
});

That will only create a date if the created: value is not present, and should prevent it from changing the creation date each time (when using .save()).

see Mongoose middleware

Rubrician answered 11/11, 2014 at 15:42 Comment(1)
Thanks a lot @cdbajorin, I have gone with the approach of checking findOne and then saving a new entry if it returns null.Judicial

© 2022 - 2024 — McMap. All rights reserved.