Rewrite hooks API

This commit breaks compatibility, but adds consistent API and allow modify data of update
This commit is contained in:
Anatoliy Chakkaev 2013-03-25 01:25:10 +04:00
parent cee7029445
commit 5ea5da0153
3 changed files with 268 additions and 46 deletions

View File

@ -74,7 +74,7 @@ AbstractClass.prototype._initProperties = function (data, applySetters) {
ctor.forEachProperty(function (attr) { ctor.forEachProperty(function (attr) {
if (!self.__data.hasOwnProperty(attr)) { if ('undefined' === typeof self.__data[attr]) {
self.__data[attr] = self.__dataWas[attr] = getDefault(attr); self.__data[attr] = self.__dataWas[attr] = getDefault(attr);
} else { } else {
self.__dataWas[attr] = self.__data[attr]; self.__dataWas[attr] = self.__data[attr];
@ -110,11 +110,11 @@ AbstractClass.prototype._initProperties = function (data, applySetters) {
return def; return def;
} }
} else { } else {
return null; return undefined;
} }
} }
this.trigger("initialize"); this.trigger('initialize');
} }
/** /**
@ -181,17 +181,21 @@ AbstractClass.create = function (data, callback) {
obj = new this(data); obj = new this(data);
data = obj.toObject(true); data = obj.toObject(true);
obj.trigger('save', function(saveDone) {
// validation required // validation required
obj.isValid(function (valid) { obj.isValid(function (valid) {
if (!valid) { if (!valid) {
callback(new Error('Validation error'), obj); callback(new Error('Validation error'), obj);
} else { } else {
create(); create(saveDone);
} }
}); });
}, obj);
} }
function create() { function create(saveDone) {
obj.trigger('create', function (done) { obj.trigger('create', function (done) {
var data = this.toObject(true); // Added this to fix the beforeCreate trigger not fire. var data = this.toObject(true); // Added this to fix the beforeCreate trigger not fire.
@ -207,12 +211,18 @@ AbstractClass.create = function (data, callback) {
obj._rev = rev obj._rev = rev
} }
done.call(this, function () { done.call(this, function () {
if (saveDone) {
saveDone.call(obj, function () {
if (callback) { if (callback) {
callback(err, obj); callback(err, obj);
} }
}); });
}.bind(this)); } else if (callback) {
callback(err, obj);
}
}); });
}.bind(this));
}, obj);
} }
}; };
@ -646,7 +656,7 @@ AbstractClass.prototype.save = function (options, callback) {
}); });
} }
}); }, this);
} }
}; };
@ -753,29 +763,31 @@ AbstractClass.prototype.updateAttributes = function updateAttributes(data, cb) {
var inst = this; var inst = this;
var model = this.constructor.modelName; var model = this.constructor.modelName;
if(!data) data = {}; if (typeof data === 'function') {
cb = data;
data = null;
}
if (!data) {
data = {};
}
// update instance's properties // update instance's properties
Object.keys(data).forEach(function (key) { Object.keys(data).forEach(function (key) {
inst[key] = data[key]; inst[key] = data[key];
}); });
inst.trigger('save', function (saveDone) {
inst.trigger('update', function (done) {
inst.isValid(function (valid) { inst.isValid(function (valid) {
if (!valid) { if (!valid) {
if (cb) { if (cb) {
cb(new Error('Validation error'), inst); cb(new Error('Validation error'), inst);
} }
} else { } else {
update();
}
});
function update() {
inst.trigger('save', function (saveDone) {
inst.trigger('update', function (done) {
Object.keys(data).forEach(function (key) { Object.keys(data).forEach(function (key) {
data[key] = inst[key]; inst[key] = data[key];
}); });
inst._adapter().updateAttributes(model, inst.id, inst.constructor._forDB(data), function (err) { inst._adapter().updateAttributes(model, inst.id, inst.constructor._forDB(data), function (err) {
@ -791,9 +803,10 @@ AbstractClass.prototype.updateAttributes = function updateAttributes(data, cb) {
}); });
}); });
}); });
}, data);
});
} }
});
}, data);
}, data);
}; };
AbstractClass.prototype.fromObject = function (obj) { AbstractClass.prototype.fromObject = function (obj) {

198
test/hooks.test.js Normal file
View File

@ -0,0 +1,198 @@
var j = require('../'),
should = require('should'),
Schema = j.Schema,
AbstractClass = j.AbstractClass,
Hookable = j.Hookable,
db, User;
describe('hooks', function() {
before(function() {
db = new Schema('memory');
User = db.define('User', {
email: String,
name: String,
password: String,
state: String
});
});
describe('initialize', function() {
afterEach(function() {
User.afterInitialize = null;
});
it('should be triggered on new', function(done) {
User.afterInitialize = function() {
done();
};
new User;
});
it('should be triggered on create', function(done) {
var user;
User.afterInitialize = function() {
if (this.name === 'Nickolay') {
this.name += ' Rozental';
}
};
User.create({name: 'Nickolay'}, function(err, u) {
u.id.should.be.a('number');
u.name.should.equal('Nickolay Rozental');
done();
});
});
});
describe('create', function() {
afterEach(removeHooks('Create'));
it('should be triggered on create', function(done) {
addHooks('Create', done);
User.create();
});
it('should not be triggered on new', function() {
User.beforeCreate = function(next) {
should.fail('This should not be called');
next();
};
var u = new User;
});
it('should be triggered on new+save', function(done) {
addHooks('Create', done);
(new User).save();
});
});
describe('save', function() {
afterEach(removeHooks('Save'));
it('should be triggered on create', function(done) {
addHooks('Save', done);
User.create();
});
it('should be triggered on new+save', function(done) {
addHooks('Save', done);
(new User).save();
});
it('should be triggered on updateAttributes', function(done) {
User.create(function(err, user) {
addHooks('Save', done);
user.updateAttributes({name: 'Anatoliy'});
});
});
it('should be triggered on save', function(done) {
User.create(function(err, user) {
addHooks('Save', done);
user.name = 'Hamburger';
user.save();
});
});
it('should save full object', function(done) {
User.create(function(err, user) {
User.beforeSave = function(next, data) {
data.toObject().should.have.keys('id', 'name', 'email',
'password', 'state')
done();
};
user.save();
});
});
});
describe('update', function() {
afterEach(removeHooks('Update'));
it('should not be triggered on create', function() {
User.beforeUpdate = function(next) {
should.fail('This should not be called');
next();
};
User.create();
});
it('should not be triggered on new+save', function() {
User.beforeUpdate = function(next) {
should.fail('This should not be called');
next();
};
(new User).save();
});
it('should be triggered on updateAttributes', function(done) {
User.create(function (err, user) {
addHooks('Update', done);
user.updateAttributes({name: 'Anatoliy'});
});
});
it('should be triggered on save', function(done) {
User.create(function (err, user) {
addHooks('Update', done);
user.name = 'Hamburger';
user.save();
});
});
it('should update limited set of fields', function(done) {
User.create(function (err, user) {
User.beforeUpdate = function(next, data) {
data.should.have.keys('name', 'email');
done();
};
user.updateAttributes({name: 1, email: 2});
});
});
});
describe('destroy', function() {
afterEach(removeHooks('Destroy'));
it('should be triggered on destroy', function() {
var hook = 'not called';
User.beforeDestroy = function() {
hook = 'called';
};
User.afterDestroy = function() {
hook.should.eql('called');
done();
};
User.create(function (err, user) {
user.destroy();
});
});
});
});
function addHooks(name, done) {
var called = false, random = Math.floor(Math.random() * 1000);
User['before' + name] = function(next, data) {
called = true;
data.email = random;
next();
};
User['after' + name] = function(next) {
(new Boolean(called)).should.equal(true);
this.email.should.equal(random);
done();
};
}
function removeHooks(name) {
return function() {
User['after' + name] = null;
User['before' + name] = null;
};
}

11
test/init.js Normal file
View File

@ -0,0 +1,11 @@
require('should');
if (!process.env.TRAVIS) {
if (typeof __cov === 'undefined') {
process.on('exit', function () {
require('semicov').report();
});
}
require('semicov').init('lib');
}