Permalink
Browse files

Async validations hooks

  • Loading branch information...
1 parent fbf1186 commit 3b2b57eb7bbb92521e7b3a8a30f62039c3ad9aa2 @1602 committed Nov 19, 2011
Showing with 136 additions and 65 deletions.
  1. +88 −61 lib/abstract-class.js
  2. +21 −4 lib/validatable.js
  3. +4 −0 test/common_test.js
  4. +23 −0 test/validations_test.coffee
View
@@ -107,26 +107,33 @@ AbstractClass.create = function (data) {
if (data instanceof AbstractClass && !data.id) {
obj = data;
data = obj.toObject(true);
+ create();
} else {
obj = new this(data);
// validation required
- if (!obj.isValid()) {
- return callback(new Error('Validation error'), obj);
- }
+ obj.isValid(function (valid) {
+ if (!valid) {
+ callback(new Error('Validation error'), obj);
+ } else {
+ create();
+ }
+ });
}
- obj.trigger("create", function () {
- this._adapter().create(modelName, data, function (err, id) {
- if (id) {
- defineReadonlyProp(obj, 'id', id);
- this.constructor.cache[id] = obj;
- }
- if (callback) {
- callback(err, obj);
- }
- }.bind(this));
- });
+ function create() {
+ obj.trigger("create", function () {
+ this._adapter().create(modelName, data, function (err, id) {
+ if (id) {
+ defineReadonlyProp(obj, 'id', id);
+ this.constructor.cache[id] = obj;
+ }
+ if (callback) {
+ callback(err, obj);
+ }
+ }.bind(this));
+ });
+ }
};
AbstractClass.exists = function exists(id, cb) {
@@ -221,34 +228,47 @@ AbstractClass.prototype.save = function (options, callback) {
options.throws = false;
}
- if (options.validate && !this.isValid()) {
- var err = new Error('Validation error');
- if (options.throws) {
- throw err;
- }
- return callback && callback(err, this);
+ if (options.validate) {
+ this.isValid(function (valid) {
+ if (valid) {
+ save.call(this);
+ } else {
+ var err = new Error('Validation error');
+ // throws option is dangerous for async usage
+ if (options.throws) {
+ throw err;
+ }
+ if (callback) {
+ callback(err, this);
+ }
+ }
+ }.bind(this));
+ } else {
+ save.call(this);
+ }
+
+ function save() {
+ this.trigger("save", function(){
+ var modelName = this.constructor.modelName;
+ var data = this.toObject(true);
+ if (this.id) {
+ this.trigger("update", function(){
+ this._adapter().save(modelName, data, function (err) {
+ if (err) {
+ console.log(err);
+ } else {
+ this.constructor.call(this, data);
+ }
+ if (callback) {
+ callback(err, this);
+ }
+ }.bind(this));
+ });
+ } else {
+ this.constructor.create(this, callback);
+ }
+ });
}
-
- this.trigger("save", function(){
- var modelName = this.constructor.modelName;
- var data = this.toObject(true);
- if (this.id) {
- this.trigger("update", function(){
- this._adapter().save(modelName, data, function (err) {
- if (err) {
- console.log(err);
- } else {
- this.constructor.call(this, data);
- }
- if (callback) {
- callback(err, this);
- }
- }.bind(this));
- });
- } else {
- this.constructor.create(this, callback);
- }
- });
};
AbstractClass.prototype.isNewRecord = function () {
@@ -295,27 +315,34 @@ AbstractClass.prototype.updateAttributes = function updateAttributes(data, cb) {
this[key] = data[key];
}.bind(this));
- if (!this.isValid()) {
- var err = new Error('Validation error');
- return cb && cb(err);
- }
-
- this.trigger("update", function(){
- this._adapter().updateAttributes(model, this.id, data, function (err) {
- if (!err) {
- Object.keys(data).forEach(function (key) {
- this[key] = data[key];
- Object.defineProperty(this, key + '_was', {
- writable: false,
- configurable: true,
- enumerable: false,
- value: data[key]
- });
- }.bind(this));
+ this.isValid(function (valid) {
+ if (!valid) {
+ if (cb) {
+ cb(new Error('Validation error'));
}
- cb(err);
- }.bind(this));
- });
+ } else {
+ update.call(this);
+ }
+ }.bind(this));
+
+ function update() {
+ this.trigger("update", function(){
+ this._adapter().updateAttributes(model, this.id, data, function (err) {
+ if (!err) {
+ Object.keys(data).forEach(function (key) {
+ this[key] = data[key];
+ Object.defineProperty(this, key + '_was', {
+ writable: false,
+ configurable: true,
+ enumerable: false,
+ value: data[key]
+ });
+ }.bind(this));
+ }
+ cb(err);
+ }.bind(this));
+ });
+ }
};
/**
View
@@ -12,7 +12,7 @@ Validatable.validatesExclusionOf = getConfigurator('exclusion');
Validatable.validatesFormatOf = getConfigurator('format');
Validatable.validate = getConfigurator('custom');
Validatable.validateAsync = getConfigurator('custom', {async: true});
-Validatable.validateUniquenessOf = getConfigurator('uniqueness');
+Validatable.validatesUniquenessOf = getConfigurator('uniqueness', {async: true});
// implementation of validators
var validators = {
@@ -70,8 +70,20 @@ var validators = {
err();
}
},
- custom: function (attr, conf, err, cb) {
- conf.customValidator.call(this, err, cb);
+ custom: function (attr, conf, err, done) {
+ conf.customValidator.call(this, err, done);
+ },
+ uniqueness: function (attr, conf, err, done) {
+ var cond = {where: {}};
+ cond.where[attr] = this[attr];
+ this.constructor.all(cond, function (error, found) {
+ if (found.length > 1) {
+ err();
+ } else if (found.length === 1 && found[0].id !== this.id) {
+ err();
+ }
+ done();
+ }.bind(this));
}
};
@@ -88,6 +100,9 @@ Validatable.prototype.isValid = function (callback) {
// exit with success when no errors
if (!this.constructor._validations) {
cleanErrors(this);
+ if (callback) {
+ callback(valid);
+ }
return valid;
}
@@ -121,6 +136,7 @@ Validatable.prototype.isValid = function (callback) {
}
if (valid) cleanErrors(this);
+ if (!async && callback) callback(valid);
return valid;
};
@@ -216,7 +232,8 @@ var defaultMessages = {
'number': 'is not a number'
},
inclusion: 'is not included in the list',
- exclusion: 'is reserved'
+ exclusion: 'is reserved',
+ uniqueness: 'is not unique'
};
function nullCheck(attr, conf, err) {
View
@@ -57,6 +57,10 @@ function testOrm(schema) {
published: { type: Boolean, default: false }
});
+ Post.validateAsync('title', function (err, done) {
+ process.nextTick(done);
+ });
+
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});
// creates instance methods:
// user.posts(conds)
@@ -27,6 +27,16 @@ validAttributes =
createdByAdmin: false
createdByScript: true
+getValidAttributes = ->
+ name: 'Anatoliy'
+ email: 'email@example.com'
+ state: ''
+ age: 26
+ gender: 'male'
+ domain: '1602'
+ createdByAdmin: false
+ createdByScript: true
+
it 'should validate presence', (test) ->
User.validatesPresenceOf 'email', 'name'
@@ -251,3 +261,16 @@ it 'should validate asynchronously', (test) ->
test.ok not valid, 'not valid name'
test.done()
+it 'should validate uniqueness', (test) ->
+ User.validatesUniquenessOf 'email'
+ User.create getValidAttributes(), ->
+ user = new User getValidAttributes()
+
+ # test.ok not user.isValid(), 'not valid because async validation'
+ user.isValid (valid) ->
+ test.ok not valid, 'email is not unique'
+ user.email = 'unique@email.tld'
+ user.isValid (valid) ->
+ test.ok valid, 'valid with unique email'
+ test.done()
+

0 comments on commit 3b2b57e

Please sign in to comment.