Conditional validations
This commit is contained in:
parent
9de9e590d6
commit
8a05e1ffad
|
@ -11,110 +11,7 @@ Validatable.validatesInclusionOf = getConfigurator('inclusion');
|
|||
Validatable.validatesExclusionOf = getConfigurator('exclusion');
|
||||
Validatable.validatesFormatOf = getConfigurator('format');
|
||||
|
||||
function getConfigurator(name) {
|
||||
return function () {
|
||||
configure(this, name, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
Validatable.prototype.isValid = function () {
|
||||
var valid = true, inst = this;
|
||||
if (!this.constructor._validations) {
|
||||
Object.defineProperty(this, 'errors', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: false
|
||||
});
|
||||
return valid;
|
||||
}
|
||||
Object.defineProperty(this, 'errors', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: new Errors
|
||||
});
|
||||
this.constructor._validations.forEach(function (v) {
|
||||
if (validationFailed(inst, v)) {
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
if (valid) {
|
||||
Object.defineProperty(this, 'errors', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: false
|
||||
});
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
|
||||
function validationFailed(inst, v) {
|
||||
var attr = v[0];
|
||||
var conf = v[1];
|
||||
// here we should check skip validation conditions (if, unless)
|
||||
// that can be specified in conf
|
||||
var fail = false;
|
||||
validators[conf.validation].call(inst, attr, conf, function onerror(kind) {
|
||||
var message;
|
||||
if (conf.message) {
|
||||
message = conf.message;
|
||||
}
|
||||
if (!message && defaultMessages[conf.validation]) {
|
||||
message = defaultMessages[conf.validation];
|
||||
}
|
||||
if (!message) {
|
||||
message = 'is invalid';
|
||||
}
|
||||
if (kind) {
|
||||
if (message[kind]) {
|
||||
// get deeper
|
||||
message = message[kind];
|
||||
} else if (defaultMessages.common[kind]) {
|
||||
message = defaultMessages.common[kind];
|
||||
}
|
||||
}
|
||||
inst.errors.add(attr, message);
|
||||
fail = true;
|
||||
});
|
||||
return fail;
|
||||
}
|
||||
|
||||
var defaultMessages = {
|
||||
presence: 'can\'t be blank',
|
||||
length: {
|
||||
min: 'too short',
|
||||
max: 'too long',
|
||||
is: 'length is wrong'
|
||||
},
|
||||
common: {
|
||||
blank: 'is blank',
|
||||
'null': 'is null'
|
||||
},
|
||||
numericality: {
|
||||
'int': 'is not an integer',
|
||||
'number': 'is not a number'
|
||||
},
|
||||
inclusion: 'is not included in the list',
|
||||
exclusion: 'is reserved'
|
||||
};
|
||||
|
||||
function nullCheck(attr, conf, err) {
|
||||
var isNull = this[attr] === null || !(attr in this);
|
||||
if (isNull) {
|
||||
if (!conf.allowNull) {
|
||||
err('null');
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (blank(this[attr])) {
|
||||
if (!conf.allowBlank) {
|
||||
err('blank');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// implementation of validators
|
||||
var validators = {
|
||||
presence: function (attr, conf, err) {
|
||||
if (blank(this[attr])) {
|
||||
|
@ -172,6 +69,136 @@ var validators = {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
function getConfigurator(name) {
|
||||
return function () {
|
||||
configure(this, name, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
Validatable.prototype.isValid = function () {
|
||||
var valid = true, inst = this;
|
||||
|
||||
// exit with success when no errors
|
||||
if (!this.constructor._validations) {
|
||||
cleanErrors(this);
|
||||
return valid;
|
||||
}
|
||||
|
||||
Object.defineProperty(this, 'errors', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: new Errors
|
||||
});
|
||||
|
||||
this.constructor._validations.forEach(function (v) {
|
||||
if (validationFailed(inst, v)) {
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
if (valid) cleanErrors(this);
|
||||
return valid;
|
||||
};
|
||||
|
||||
function cleanErrors(inst) {
|
||||
Object.defineProperty(inst, 'errors', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: false
|
||||
});
|
||||
}
|
||||
|
||||
function validationFailed(inst, v) {
|
||||
var attr = v[0];
|
||||
var conf = v[1];
|
||||
|
||||
// here we should check skip validation conditions (if, unless)
|
||||
// that can be specified in conf
|
||||
if (skipValidation(inst, conf, 'if')) return false;
|
||||
if (skipValidation(inst, conf, 'unless')) return false;
|
||||
|
||||
var fail = false;
|
||||
validators[conf.validation].call(inst, attr, conf, function onerror(kind) {
|
||||
var message;
|
||||
if (conf.message) {
|
||||
message = conf.message;
|
||||
}
|
||||
if (!message && defaultMessages[conf.validation]) {
|
||||
message = defaultMessages[conf.validation];
|
||||
}
|
||||
if (!message) {
|
||||
message = 'is invalid';
|
||||
}
|
||||
if (kind) {
|
||||
if (message[kind]) {
|
||||
// get deeper
|
||||
message = message[kind];
|
||||
} else if (defaultMessages.common[kind]) {
|
||||
message = defaultMessages.common[kind];
|
||||
}
|
||||
}
|
||||
inst.errors.add(attr, message);
|
||||
fail = true;
|
||||
});
|
||||
return fail;
|
||||
}
|
||||
|
||||
function skipValidation(inst, conf, kind) {
|
||||
var doValidate = true;
|
||||
if (typeof conf[kind] === 'function') {
|
||||
doValidate = conf[kind].call(inst);
|
||||
if (kind === 'unless') doValidate = !doValidate;
|
||||
} else if (typeof conf[kind] === 'string') {
|
||||
if (inst.hasOwnProperty(conf[kind])) {
|
||||
doValidate = inst[conf[kind]];
|
||||
if (kind === 'unless') doValidate = !doValidate;
|
||||
} else if (typeof inst[conf[kind]] === 'function') {
|
||||
doValidate = inst[conf[kind]].call(inst);
|
||||
if (kind === 'unless') doValidate = !doValidate;
|
||||
} else {
|
||||
doValidate = kind === 'if';
|
||||
}
|
||||
}
|
||||
return !doValidate;
|
||||
}
|
||||
|
||||
var defaultMessages = {
|
||||
presence: 'can\'t be blank',
|
||||
length: {
|
||||
min: 'too short',
|
||||
max: 'too long',
|
||||
is: 'length is wrong'
|
||||
},
|
||||
common: {
|
||||
blank: 'is blank',
|
||||
'null': 'is null'
|
||||
},
|
||||
numericality: {
|
||||
'int': 'is not an integer',
|
||||
'number': 'is not a number'
|
||||
},
|
||||
inclusion: 'is not included in the list',
|
||||
exclusion: 'is reserved'
|
||||
};
|
||||
|
||||
function nullCheck(attr, conf, err) {
|
||||
var isNull = this[attr] === null || !(attr in this);
|
||||
if (isNull) {
|
||||
if (!conf.allowNull) {
|
||||
err('null');
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (blank(this[attr])) {
|
||||
if (!conf.allowBlank) {
|
||||
err('blank');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function blank(v) {
|
||||
if (typeof v === 'undefined') return true;
|
||||
if (v instanceof Array && v.length === 0) return true;
|
||||
|
|
|
@ -14,6 +14,8 @@ User = schema.define 'User',
|
|||
age: Number
|
||||
gender: String
|
||||
domain: String
|
||||
pendingPeriod: Number
|
||||
createdByAdmin: Boolean
|
||||
|
||||
validAttributes =
|
||||
name: 'Anatoliy'
|
||||
|
@ -22,11 +24,12 @@ validAttributes =
|
|||
age: 26
|
||||
gender: 'male'
|
||||
domain: '1602'
|
||||
|
||||
User.validatesPresenceOf 'email', 'name'
|
||||
|
||||
createdByAdmin: false
|
||||
createdByScript: true
|
||||
|
||||
it 'should validate presence', (test) ->
|
||||
User.validatesPresenceOf 'email', 'name'
|
||||
|
||||
user = new User
|
||||
test.ok not user.isValid(), 'User is not valid'
|
||||
test.ok user.errors.email, 'Attr email in errors'
|
||||
|
@ -44,6 +47,36 @@ it 'should validate presence', (test) ->
|
|||
test.ok not user.errors.name, 'Attr name valid'
|
||||
test.done()
|
||||
|
||||
it 'should allow to skip validations', (test) ->
|
||||
User.validatesPresenceOf 'pendingPeriod', if: 'createdByAdmin'
|
||||
User.validatesLengthOf 'domain', is: 2, unless: 'createdByScript'
|
||||
|
||||
user = new User validAttributes
|
||||
test.ok user.isValid()
|
||||
|
||||
user.createdByAdmin = true
|
||||
test.ok not user.isValid()
|
||||
test.ok user.errors.pendingPeriod.length
|
||||
|
||||
user.pendingPeriod = 1
|
||||
test.ok user.isValid()
|
||||
|
||||
user.createdByScript = false
|
||||
test.ok not user.isValid()
|
||||
test.ok user.errors.domain.length
|
||||
|
||||
user.domain = '12'
|
||||
test.ok user.isValid()
|
||||
|
||||
User.validatesLengthOf 'domain', is: 3, unless: -> @domain != 'xyz'
|
||||
test.ok user.isValid()
|
||||
|
||||
user.domain = 'xyz'
|
||||
test.ok not user.isValid() # is: 3 passed, but is: 2 failed
|
||||
|
||||
|
||||
test.done()
|
||||
|
||||
|
||||
it 'should throw error on save if required', (test) ->
|
||||
user = new User
|
||||
|
|
Loading…
Reference in New Issue