Async validations hooks

This commit is contained in:
Anatoliy Chakkaev 2011-11-19 15:58:49 +07:00
parent fbf118695e
commit 3b2b57eb7b
4 changed files with 136 additions and 65 deletions

View File

@ -107,15 +107,21 @@ 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();
}
});
}
function create() {
obj.trigger("create", function () {
this._adapter().create(modelName, data, function (err, id) {
if (id) {
@ -127,6 +133,7 @@ AbstractClass.create = function (data) {
}
}.bind(this));
});
}
};
AbstractClass.exists = function exists(id, cb) {
@ -221,14 +228,26 @@ AbstractClass.prototype.save = function (options, callback) {
options.throws = false;
}
if (options.validate && !this.isValid()) {
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;
}
return callback && callback(err, this);
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);
@ -249,6 +268,7 @@ AbstractClass.prototype.save = function (options, callback) {
this.constructor.create(this, callback);
}
});
}
};
AbstractClass.prototype.isNewRecord = function () {
@ -295,11 +315,17 @@ 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.isValid(function (valid) {
if (!valid) {
if (cb) {
cb(new Error('Validation error'));
}
} else {
update.call(this);
}
}.bind(this));
function update() {
this.trigger("update", function(){
this._adapter().updateAttributes(model, this.id, data, function (err) {
if (!err) {
@ -316,6 +342,7 @@ AbstractClass.prototype.updateAttributes = function updateAttributes(data, cb) {
cb(err);
}.bind(this));
});
}
};
/**

View File

@ -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 File

@ -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)

View File

@ -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()