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) { if (data instanceof AbstractClass && !data.id) {
obj = data; obj = data;
data = obj.toObject(true); data = obj.toObject(true);
create();
} else { } else {
obj = new this(data); obj = new this(data);
// validation required // validation required
if (!obj.isValid()) { obj.isValid(function (valid) {
return callback(new Error('Validation error'), obj); if (!valid) {
callback(new Error('Validation error'), obj);
} else {
create();
} }
});
} }
function create() {
obj.trigger("create", function () { obj.trigger("create", function () {
this._adapter().create(modelName, data, function (err, id) { this._adapter().create(modelName, data, function (err, id) {
if (id) { if (id) {
@ -127,6 +133,7 @@ AbstractClass.create = function (data) {
} }
}.bind(this)); }.bind(this));
}); });
}
}; };
AbstractClass.exists = function exists(id, cb) { AbstractClass.exists = function exists(id, cb) {
@ -221,14 +228,26 @@ AbstractClass.prototype.save = function (options, callback) {
options.throws = false; 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'); var err = new Error('Validation error');
// throws option is dangerous for async usage
if (options.throws) { if (options.throws) {
throw err; throw err;
} }
return callback && callback(err, this); if (callback) {
callback(err, this);
}
}
}.bind(this));
} else {
save.call(this);
} }
function save() {
this.trigger("save", function(){ this.trigger("save", function(){
var modelName = this.constructor.modelName; var modelName = this.constructor.modelName;
var data = this.toObject(true); var data = this.toObject(true);
@ -249,6 +268,7 @@ AbstractClass.prototype.save = function (options, callback) {
this.constructor.create(this, callback); this.constructor.create(this, callback);
} }
}); });
}
}; };
AbstractClass.prototype.isNewRecord = function () { AbstractClass.prototype.isNewRecord = function () {
@ -295,11 +315,17 @@ AbstractClass.prototype.updateAttributes = function updateAttributes(data, cb) {
this[key] = data[key]; this[key] = data[key];
}.bind(this)); }.bind(this));
if (!this.isValid()) { this.isValid(function (valid) {
var err = new Error('Validation error'); if (!valid) {
return cb && cb(err); if (cb) {
cb(new Error('Validation error'));
} }
} else {
update.call(this);
}
}.bind(this));
function update() {
this.trigger("update", function(){ this.trigger("update", function(){
this._adapter().updateAttributes(model, this.id, data, function (err) { this._adapter().updateAttributes(model, this.id, data, function (err) {
if (!err) { if (!err) {
@ -316,6 +342,7 @@ AbstractClass.prototype.updateAttributes = function updateAttributes(data, cb) {
cb(err); cb(err);
}.bind(this)); }.bind(this));
}); });
}
}; };
/** /**

View File

@ -12,7 +12,7 @@ Validatable.validatesExclusionOf = getConfigurator('exclusion');
Validatable.validatesFormatOf = getConfigurator('format'); Validatable.validatesFormatOf = getConfigurator('format');
Validatable.validate = getConfigurator('custom'); Validatable.validate = getConfigurator('custom');
Validatable.validateAsync = getConfigurator('custom', {async: true}); Validatable.validateAsync = getConfigurator('custom', {async: true});
Validatable.validateUniquenessOf = getConfigurator('uniqueness'); Validatable.validatesUniquenessOf = getConfigurator('uniqueness', {async: true});
// implementation of validators // implementation of validators
var validators = { var validators = {
@ -70,8 +70,20 @@ var validators = {
err(); err();
} }
}, },
custom: function (attr, conf, err, cb) { custom: function (attr, conf, err, done) {
conf.customValidator.call(this, err, cb); 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 // exit with success when no errors
if (!this.constructor._validations) { if (!this.constructor._validations) {
cleanErrors(this); cleanErrors(this);
if (callback) {
callback(valid);
}
return valid; return valid;
} }
@ -121,6 +136,7 @@ Validatable.prototype.isValid = function (callback) {
} }
if (valid) cleanErrors(this); if (valid) cleanErrors(this);
if (!async && callback) callback(valid);
return valid; return valid;
}; };
@ -216,7 +232,8 @@ var defaultMessages = {
'number': 'is not a number' 'number': 'is not a number'
}, },
inclusion: 'is not included in the list', inclusion: 'is not included in the list',
exclusion: 'is reserved' exclusion: 'is reserved',
uniqueness: 'is not unique'
}; };
function nullCheck(attr, conf, err) { function nullCheck(attr, conf, err) {

View File

@ -57,6 +57,10 @@ function testOrm(schema) {
published: { type: Boolean, default: false } published: { type: Boolean, default: false }
}); });
Post.validateAsync('title', function (err, done) {
process.nextTick(done);
});
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}); User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});
// creates instance methods: // creates instance methods:
// user.posts(conds) // user.posts(conds)

View File

@ -27,6 +27,16 @@ validAttributes =
createdByAdmin: false createdByAdmin: false
createdByScript: true 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) -> it 'should validate presence', (test) ->
User.validatesPresenceOf 'email', 'name' User.validatesPresenceOf 'email', 'name'
@ -251,3 +261,16 @@ it 'should validate asynchronously', (test) ->
test.ok not valid, 'not valid name' test.ok not valid, 'not valid name'
test.done() 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()