Merge pull request #174 from fabien/fix/validations

Fix/validations
This commit is contained in:
Raymond Feng 2014-07-15 12:53:33 -07:00
commit efeb59178f
2 changed files with 111 additions and 6 deletions

View File

@ -46,6 +46,20 @@ function Validatable() {
*/ */
Validatable.validatesPresenceOf = getConfigurator('presence'); Validatable.validatesPresenceOf = getConfigurator('presence');
/**
* Validate absence of one or more specified properties.
* A model should not include a property to be considered valid; fails when validated field not blank.
*
* For example, validate absence of reserved
* ```
* Post.validatesAbsenceOf('reserved', { unless: 'special' });
*
* @param {String} propertyName One or more property names.
* @options {Object} errMsg Optional custom error message. Default is "can't be set"
* @property {String} message Error message to use instead of default.
*/
Validatable.validatesAbsenceOf = getConfigurator('absence');
/** /**
* Validate length. Require a property length to be within a specified range. * Validate length. Require a property length to be within a specified range.
* Three kinds of validations: min, max, is. * Three kinds of validations: min, max, is.
@ -225,6 +239,15 @@ function validatePresence(attr, conf, err) {
} }
} }
/*!
* Absence validator
*/
function validateAbsence(attr, conf, err) {
if (!blank(this[attr])) {
err();
}
}
/*! /*!
* Length validator * Length validator
*/ */
@ -305,6 +328,9 @@ function validateCustom(attr, conf, err, done) {
* Uniqueness validator * Uniqueness validator
*/ */
function validateUniqueness(attr, conf, err, done) { function validateUniqueness(attr, conf, err, done) {
if (blank(this[attr])) {
return process.nextTick(done);
}
var cond = {where: {}}; var cond = {where: {}};
cond.where[attr] = this[attr]; cond.where[attr] = this[attr];
@ -331,6 +357,7 @@ function validateUniqueness(attr, conf, err, done) {
var validators = { var validators = {
presence: validatePresence, presence: validatePresence,
absence: validateAbsence,
length: validateLength, length: validateLength,
numericality: validateNumericality, numericality: validateNumericality,
inclusion: validateInclusion, inclusion: validateInclusion,
@ -469,8 +496,11 @@ function validationFailed(inst, v, cb) {
// here we should check skip validation conditions (if, unless) // here we should check skip validation conditions (if, unless)
// that can be specified in conf // that can be specified in conf
if (skipValidation(inst, conf, 'if')) return false; if (skipValidation(inst, conf, 'if')
if (skipValidation(inst, conf, 'unless')) return false; || skipValidation(inst, conf, 'unless')) {
if (cb) cb(true);
return false;
}
var fail = false; var fail = false;
var validator = validators[conf.validation]; var validator = validators[conf.validation];
@ -478,7 +508,7 @@ function validationFailed(inst, v, cb) {
validatorArguments.push(attr); validatorArguments.push(attr);
validatorArguments.push(conf); validatorArguments.push(conf);
validatorArguments.push(function onerror(kind) { validatorArguments.push(function onerror(kind) {
var message, code = conf.validation; var message, code = conf.code || conf.validation;
if (conf.message) { if (conf.message) {
message = conf.message; message = conf.message;
} }
@ -499,7 +529,7 @@ function validationFailed(inst, v, cb) {
message = 'is invalid'; message = 'is invalid';
} }
} }
inst.errors.add(attr, message, code); if (kind !== false) inst.errors.add(attr, message, code);
fail = true; fail = true;
}); });
if (cb) { if (cb) {
@ -532,6 +562,7 @@ function skipValidation(inst, conf, kind) {
var defaultMessages = { var defaultMessages = {
presence: 'can\'t be blank', presence: 'can\'t be blank',
absence: 'can\'t be set',
length: { length: {
min: 'too short', min: 'too short',
max: 'too long', max: 'too long',

View File

@ -159,6 +159,20 @@ describe('validations', function () {
}); });
}); });
describe('absence', function () {
it('should validate absence', function () {
User.validatesAbsenceOf('reserved', { if: 'locked' });
var u = new User({reserved: 'foo', locked: true});
u.isValid().should.not.be.true;
u.reserved = null;
u.isValid().should.be.true;
var u = new User({reserved: 'foo', locked: false});
u.isValid().should.be.true;
});
});
describe('uniqueness', function () { describe('uniqueness', function () {
it('should validate uniqueness', function (done) { it('should validate uniqueness', function (done) {
@ -227,6 +241,33 @@ describe('validations', function () {
done(err); done(err);
}); });
}); });
it('should skip blank values', function (done) {
User.validatesUniquenessOf('email');
var u = new User({email: ' '});
Boolean(u.isValid(function (valid) {
valid.should.be.true;
u.save(function () {
var u2 = new User({email: null});
u2.isValid(function (valid) {
valid.should.be.true;
done();
});
});
})).should.be.false;
});
it('should work with if/unless', function (done) {
User.validatesUniquenessOf('email', {
if: function() { return true; },
unless: function() { return false; }
});
var u = new User({email: 'hello'});
Boolean(u.isValid(function (valid) {
valid.should.be.true;
done();
})).should.be.false;
});
}); });
describe('format', function () { describe('format', function () {
@ -251,7 +292,40 @@ describe('validations', function () {
}); });
describe('custom', function () { describe('custom', function () {
it('should validate using custom sync validation'); it('should validate using custom sync validation', function() {
it('should validate using custom async validation'); User.validate('email', function (err) {
if (this.email === 'hello') err();
}, { code: 'invalid-email' });
var u = new User({email: 'hello'});
Boolean(u.isValid()).should.be.false;
u.errors.codes.should.eql({ email: ['invalid-email'] });
});
it('should validate and return detailed error messages', function() {
User.validate('global', function (err) {
if (this.email === 'hello' || this.email === 'hey') {
this.errors.add('email', 'Cannot be `' + this.email + '`', 'invalid-email');
err(false); // false: prevent global error message
}
});
var u = new User({email: 'hello'});
Boolean(u.isValid()).should.be.false;
u.errors.should.eql({ email: ['Cannot be `hello`'] });
u.errors.codes.should.eql({ email: ['invalid-email'] });
});
it('should validate using custom async validation', function(done) {
User.validateAsync('email', function (err, next) {
process.nextTick(next);
}, {
if: function() { return true; },
unless: function() { return false; }
});
var u = new User({email: 'hello'});
Boolean(u.isValid(function (valid) {
valid.should.be.true;
done();
})).should.be.false;
});
}); });
}); });