Skip to content
Browse files

Conditional validations

  • Loading branch information...
1 parent 9de9e59 commit 8a05e1ffadc99f375dd77dcf4765a5f7c1bae614 @1602 committed Oct 11, 2011
Showing with 132 additions and 72 deletions.
  1. +96 −69 lib/validatable.js
  2. +36 −3 test/validations_test.coffee
View
165 lib/validatable.js
@@ -11,6 +11,65 @@ Validatable.validatesInclusionOf = getConfigurator('inclusion');
Validatable.validatesExclusionOf = getConfigurator('exclusion');
Validatable.validatesFormatOf = getConfigurator('format');
+// implementation of validators
+var validators = {
+ presence: function (attr, conf, err) {
+ if (blank(this[attr])) {
+ err();
+ }
+ },
+ length: function (attr, conf, err) {
+ if (nullCheck.call(this, attr, conf, err)) return;
+
+ var len = this[attr].length;
+ if (conf.min && len < conf.min) {
+ err('min');
+ }
+ if (conf.max && len > conf.max) {
+ err('max');
+ }
+ if (conf.is && len !== conf.is) {
+ err('is');
+ }
+ },
+ numericality: function (attr, conf, err) {
+ if (nullCheck.call(this, attr, conf, err)) return;
+
+ if (typeof this[attr] !== 'number') {
+ return err('number');
+ }
+ if (conf.int && this[attr] !== Math.round(this[attr])) {
+ return err('int');
+ }
+ },
+ inclusion: function (attr, conf, err) {
+ if (nullCheck.call(this, attr, conf, err)) return;
+
+ if (!~conf.in.indexOf(this[attr])) {
+ err()
+ }
+ },
+ exclusion: function (attr, conf, err) {
+ if (nullCheck.call(this, attr, conf, err)) return;
+
+ if (~conf.in.indexOf(this[attr])) {
+ err()
+ }
+ },
+ format: function (attr, conf, err) {
+ if (nullCheck.call(this, attr, conf, err)) return;
+
+ if (typeof this[attr] === 'string') {
+ if (!this[attr].match(conf['with'])) {
+ err();
+ }
+ } else {
+ err();
+ }
+ }
+};
+
+
function getConfigurator(name) {
return function () {
configure(this, name, arguments);
@@ -19,39 +78,45 @@ function getConfigurator(name) {
Validatable.prototype.isValid = function () {
var valid = true, inst = this;
+
+ // exit with success when no errors
if (!this.constructor._validations) {
- Object.defineProperty(this, 'errors', {
- enumerable: false,
- configurable: true,
- value: false
- });
+ 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) {
- Object.defineProperty(this, 'errors', {
- enumerable: false,
- configurable: true,
- value: 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;
@@ -78,6 +143,25 @@ function validationFailed(inst, v) {
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: {
@@ -115,63 +199,6 @@ function nullCheck(attr, conf, err) {
return false;
}
-var validators = {
- presence: function (attr, conf, err) {
- if (blank(this[attr])) {
- err();
- }
- },
- length: function (attr, conf, err) {
- if (nullCheck.call(this, attr, conf, err)) return;
-
- var len = this[attr].length;
- if (conf.min && len < conf.min) {
- err('min');
- }
- if (conf.max && len > conf.max) {
- err('max');
- }
- if (conf.is && len !== conf.is) {
- err('is');
- }
- },
- numericality: function (attr, conf, err) {
- if (nullCheck.call(this, attr, conf, err)) return;
-
- if (typeof this[attr] !== 'number') {
- return err('number');
- }
- if (conf.int && this[attr] !== Math.round(this[attr])) {
- return err('int');
- }
- },
- inclusion: function (attr, conf, err) {
- if (nullCheck.call(this, attr, conf, err)) return;
-
- if (!~conf.in.indexOf(this[attr])) {
- err()
- }
- },
- exclusion: function (attr, conf, err) {
- if (nullCheck.call(this, attr, conf, err)) return;
-
- if (~conf.in.indexOf(this[attr])) {
- err()
- }
- },
- format: function (attr, conf, err) {
- if (nullCheck.call(this, attr, conf, err)) return;
-
- if (typeof this[attr] === 'string') {
- if (!this[attr].match(conf['with'])) {
- err();
- }
- } else {
- err();
- }
- }
-};
-
function blank(v) {
if (typeof v === 'undefined') return true;
if (v instanceof Array && v.length === 0) return true;
View
39 test/validations_test.coffee
@@ -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

0 comments on commit 8a05e1f

Please sign in to comment.
Something went wrong with that request. Please try again.