Jdruwe
Jdruwe

Reputation: 3520

Mongoose findOneAndUpdate and runValidators not working

I am having issues trying to get the 'runValidators' option to work. My user schema has an email field that has required set to true but each time a new user gets added to the database (using the 'upsert' option) and the email field is empty it does not complain:

 var userSchema = new mongoose.Schema({
   facebookId: {type: Number, required: true},
   activated: {type: Boolean, required: true, default: false},
   email: {type: String, required: true}
});

findOneAndUpdate code:

model.user.user.findOneAndUpdate(
      {facebookId: request.params.facebookId},
      {
          $setOnInsert: {
              facebookId: request.params.facebookId,
              email: request.payload.email,
          }
      },
      {upsert: true, 
       new: true, 
       runValidators: true, 
       setDefaultsOnInsert: true
      }, function (err, user) {
          if (err) {
              console.log(err);
              return reply(boom.badRequest(authError));
          }
          return reply(user);
      });

I have no idea what I am doing wrong, I just followed the docs: http://mongoosejs.com/docs/validation.html

In the docs is says the following:

Note that in mongoose 4.x, update validators only run on $set and $unset operations. For instance, the below update will succeed, regardless of the value of number.

I replaced the $setOnInsert with $set but had the same result.

Upvotes: 35

Views: 16684

Answers (7)

Paul Serre
Paul Serre

Reputation: 800

The reason behind this behavior is that mongoose assumes you are just going to update the document, not insert one. The only possibility of having an invalid model with upsert is therefore to perform an $unset. In other words, findOneAndUpdate would be appropriate for a PATCH endpoint. If you want to validate the model on insert, and be able to perform a update on this endpoint too (it would be a PUT endpoint) you should use replaceOne

Upvotes: 1

Yunus Ccn
Yunus Ccn

Reputation: 1

If you want to validate with findOneAndUpdate you can not get current document but you can get this keywords's contents and in this keywords's content have "op" property so solution is this :


Note : does not matter if you use context or not. Also, don't forget to send data include both "price" and "priceDiscount" in findOneAndUpdate body.

validate: {
        validator: function (value) {
          if (this.op === 'findOneAndUpdate') {
            console.log(this.getUpdate().$set.priceDiscount);
            console.log(this.getUpdate().$set.price);
            return (
              this.getUpdate().$set.priceDiscount < this.getUpdate().$set.price
            );
          }
          return value < this.price;
        },
        message: 'Discount price ({VALUE}) should be below regular price',
      }

Upvotes: 0

TodorBalabanski
TodorBalabanski

Reputation: 119

I fixed the issue by adding a pre hook for findOneAndUpdate():

ExampleSchema.pre('findOneAndUpdate', function (next) {
    this.options.runValidators = true
    next()
})

Then when I am using findOneAndUpdate the validation is working.

Upvotes: 4

Isaac Pak
Isaac Pak

Reputation: 4931

use this plugin: mongoose-unique-validator

When using methods like findOneAndUpdate you will need to pass this configuration object:

{ runValidators: true, context: 'query' }

ie.

User.findOneAndUpdate(
  { email: '[email protected]' },
  { email: '[email protected]' },
  { runValidators: true, context: 'query' },
  function(err) {
    // ...
}

Upvotes: 7

Kevin Curry
Kevin Curry

Reputation: 117

required validators only fail when you try to explicitly $unset the key.

This makes no sense to me but it's what the docs say.

Upvotes: 10

steampowered
steampowered

Reputation: 12062

I created a plugin to validate required model properties before doing update operations in mongoose.

Plugin code here

var mongoose = require('mongoose');
var _ = require('lodash');
var s = require('underscore.string');

function validateExtra(schema, options){
    schema.methods.validateRequired = function(){
        var deferred = Promise.defer();
        var self = this;
        try {
            _.forEach(this.schema.paths, function (val, key) {
                if (val.isRequired && _.isUndefined(self[key])) {
                    throw new Error(s.humanize(key) + ' is not set and is required');
                }
            });
            deferred.resolve();
        } catch (err){
            deferred.reject(err);
        }
        return deferred.promise;
    }
}

module.exports = validateExtra;

Must be called explicitly as a method from the model, so I recommend chaining it a .then chain prior to the update call.

Plugin in use here

fuelOrderModel(postVars.fuelOrder).validateRequired()
    .then(function(){
        return fuelOrderModel.findOneAndUpdate({_id: postVars.fuelOrder.fuelOrderId}, 
           postVars.fuelOrder, {runValidators: true, upsert: true, 
           setDefaultsOnInsert: true, new: true})
                .then(function(doc) {
                    res.json({fuelOrderId: postVars.fuelOrder.fuelOrderId});
                });
    }, function(err){
            global.saveError(err, 'server', req.user);
            res.status(500).json(err);
    });

Upvotes: 1

yojna
yojna

Reputation: 513

In mongoose do same thing in two step.

  1. Find the result using findOne() method.
  2. Add fields and save document using Model.save().

This will update your document.

Upvotes: 4

Related Questions