Mongoose: Validation not working on undefined model properties

Created on 30 May 2012  路  14Comments  路  Source: Automattic/mongoose

Hey,

Not sure if this is working as intended or not, but I noticed that if I instantiate a model then immediately save it, the validation rules on the individual properties are not run.

I created a small script below to demonstrate what I mean. If you run this with mocha, the first test on the user model will fail because err is null after the save, when I was expecting the validation to fail because user.email == undefined.

var mongoose = require('mongoose'),
    Schema = mongoose.Schema,
    should = require('should'),
    UserSchema,
    User;

mongoose.connect('mongodb://localhost/test');

function validatePresenceOf (value) {
    if(typeof value === 'string' || typeof value === 'number') {
        value = value.toString().trim();
    }
    return !!(value && value.length);
}

UserSchema = new Schema({
    email : {type: String, 
        validate: [validatePresenceOf, "email required"],
        index: {unique: true}
    },
    hashed_password : String,
    salt : String
});

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

describe('validatePresenceOf', function () {
    it("should return false for undefined", function () {
        validatePresenceOf(undefined).should.be.false;
    });

    it("should return false for empty string", function () {
        validatePresenceOf('').should.be.false;
    })
})

describe("User model", function () {
    describe("validation", function () {
        afterEach(function () {
            User.find().remove();
        });

        it(" does not validate if user.email is not set first", function (done) {
            var user = new User();

            // expecting validation to fail because validatePresenceOf returns
            // false, but err is null
            user.save(function (err) {
                should.exist(err);
                err.toString().should.equal('ValidationError: Validator ' + 
                    '"email required" failed for path email');
                done();
            });
        });

        it("is fine if I set user.email", function (done) {
            var user = new User();
            user.email = '';

            // validation works fine if I set user.email before saving
            user.save(function (err) {
                should.exist(err);
                err.toString().should.equal('ValidationError: Validator ' + 
                    '"email required" failed for path email');
                done();
            });
        });
    });
});
enhancement

Most helpful comment

There should not need to be a trick to make it work.

All 14 comments

if a path is required use the required validator.

new Schema({
    email : {type: String, 
        validate: [validatePresenceOf, "email required"],
        required: true,
        index: {unique: true}
    },
    hashed_password : String,
    salt : String
});

Ah, awesome thanks for the help!
On May 30, 2012 11:26 AM, "Aaron Heckmann" <
[email protected]>
wrote:

if a path is required use the required validator.

new Schema({
   email : {type: String,
       validate: [validatePresenceOf, "email required"],
       required: true,
       index: {unique: true}
   },
   hashed_password : String,
   salt : String
});

---
Reply to this email directly or view it on GitHub:
https://github.com/LearnBoost/mongoose/issues/941#issuecomment-6012750

this is a bit old but how do you perform conditional required based on another field?
I added this custom validator:

        validate: [
            function validator(val) {
                return this.type === 'other' && val === '';
            }, '{PATH} is required'
        ]

but if property is not existent this is never run... any workaround?

I have exactly the same problem as @cyberval

UPD:
In the meantime I came up with this ugly hack (real code snippet):

schema.path('frequency').validators.push([function(val){
  // frequency is not required for singular sessions
  if(!this.multiSession) return true;
  return !!val;
}, mongoose.Error.messages.general.required, 'required']);
schema.path('frequency').isRequired = true;

Seems to work. But yeah, having custom conditional required validation would be nice.

:+1:

Just spent the better part of an hours thinking I was doing something wrong. Thanks for the hack @chopachom

The trick I used for conditional 'required' validation was this to default to null and use a custom validator. This will force the validator to be called even if the field is not set.
...
default: null,
validate: [customValidator...]

There should not need to be a trick to make it work.

@jonstorer agreed. I have the same issue as @cyberval. We should open a new issue.

has this been solved ?

@cyberval yeah, required can now take a function. See #2247

I'm not sure it is working. If the property is defined then yes the function will be executed but if the property is not defined then the required function won't be triggered and required is set to true somewhere

@cyberval passing a function to required is supported, but running validators on undefined fields is not supported and currently not planned, see #2449 #2446

Here's another way to do it. Add the validator into some pre validate middleware:

schema.pre('validate', function(next) {
  //3, or whatever value you're checking for
  if (this.status === 3) {
    return next();
  }
  if (this.myConditionalField) {
    return next();
  }
  var error = new mongoose.Error.ValidationError(this);
  error.errors.myConditionalField = new mongoose.Error.ValidatorError('myConditionalField', 'myConditionalField is required for status passed.', 'notvalid', this.myConditionalField);
  return next(error);
});

use default: null
worked for me.

Was this page helpful?
0 / 5 - 0 ratings