diff --git a/lib/abstract-class.js b/lib/abstract-class.js index a877399b..f11bef1a 100644 --- a/lib/abstract-class.js +++ b/lib/abstract-class.js @@ -2,12 +2,14 @@ * Module deps */ var Validatable = require('./validatable').Validatable; +var Hookable = require('./hookable').Hookable; var util = require('util'); var jutil = require('./jutil'); exports.AbstractClass = AbstractClass; jutil.inherits(AbstractClass, Validatable); +jutil.inherits(AbstractClass, Hookable); /** * Abstract class constructor @@ -79,6 +81,8 @@ function AbstractClass(data) { return null; } } + + this.trigger("initialize"); }; /** @@ -107,21 +111,24 @@ AbstractClass.create = function (data) { obj = new this(data); // validation required - if (!obj.isValid()) { - return callback(new Error('Validation error'), obj); - } + obj.trigger("validation", function(){ + if (!obj.isValid()) { + return callback(new Error('Validation error'), obj); + } + }); } - this.schema.adapter.create(modelName, data, function (err, id) { - if (id) { - defineReadonlyProp(obj, 'id', id); - this.cache[id] = obj; - } - if (callback) { - callback(err, obj); - } - }.bind(this)); - + 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) { @@ -213,29 +220,36 @@ AbstractClass.prototype.save = function (options, callback) { if (!('throws' in options)) { options.throws = false; } - if (options.validate && !this.isValid()) { - var err = new Error('Validation error'); - if (options.throws) { - throw err; + this.trigger("validation", function(){ + if (options.validate && !this.isValid()) { + var err = new Error('Validation error'); + if (options.throws) { + throw err; + } + return callback && callback(err); } - return callback && callback(err); - } - var modelName = this.constructor.modelName; - var data = this.toObject(true); - if (this.id) { - 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 () { @@ -262,10 +276,12 @@ AbstractClass.prototype.toObject = function (onlySchema) { }; AbstractClass.prototype.destroy = function (cb) { - this._adapter().destroy(this.constructor.modelName, this.id, function (err) { - delete this.constructor.cache[this.id]; - cb && cb(err); - }.bind(this)); + this.trigger("destroy", function(){ + this._adapter().destroy(this.constructor.modelName, this.id, function (err) { + delete this.constructor.cache[this.id]; + cb && cb(err); + }.bind(this)); + }); }; AbstractClass.prototype.updateAttribute = function (name, value, cb) { @@ -279,24 +295,29 @@ AbstractClass.prototype.updateAttributes = function updateAttributes(data, cb) { Object.keys(data).forEach(function (key) { this[key] = data[key]; }.bind(this)); - if (!this.isValid()) { - var err = new Error('Validation error'); - return cb && cb(err); - } - 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.trigger("validation", function(){ + if (!this.isValid()) { + var err = new Error('Validation error'); + return cb && cb(err); } - cb(err); - }.bind(this)); + }); + + 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)); + }); }; /** diff --git a/lib/hookable.js b/lib/hookable.js new file mode 100644 index 00000000..38b0feb0 --- /dev/null +++ b/lib/hookable.js @@ -0,0 +1,32 @@ +exports.Hookable = Hookable; + +function Hookable() { + // hookable class +}; + +Hookable.afterInitialize = function(){}; +Hookable.beforeValidation = function(){}; +Hookable.afterValidation = function(){}; +Hookable.beforeSave = function(){}; +Hookable.afterSave = function(){}; +Hookable.beforeCreate = function(){}; +Hookable.afterCreate = function(){}; +Hookable.beforeUpdate = function(){}; +Hookable.afterUpdate = function(){}; +Hookable.beforeDestroy = function(){}; +Hookable.afterDestroy = function(){}; + +Hookable.prototype.trigger = function(action, work){ + if(work){ + bHook = this.constructor["before" + capitalize(action)]; + if(bHook) bHook.call( this ); + + work.call(this) + } + aHook = this.constructor["after" + capitalize(action)]; + if(aHook) aHook.call( this ); +} + +function capitalize(string){ + return string.charAt(0).toUpperCase() + string.slice(1); +} \ No newline at end of file diff --git a/test/hookable_test.coffee b/test/hookable_test.coffee new file mode 100644 index 00000000..df04a902 --- /dev/null +++ b/test/hookable_test.coffee @@ -0,0 +1,79 @@ +juggling = require('../index') +Schema = juggling.Schema +AbstractClass = juggling.AbstractClass +Hookable = juggling.Hookable + +require('./spec_helper').init module.exports + +schema = new Schema 'memory' +User = schema.define 'User', + email: String + name: String + password: String + state: String + age: Number + gender: String + domain: String + pendingPeriod: Number + createdByAdmin: Boolean + +it "should trigger after initialize", (test)-> + User.afterInitialize = ()-> + User.afterInitialize = null + test.done() + user = new User + + +it "should trigger before create", (test)-> + User.beforeCreate = ()-> + User.beforeCreate = null + test.done() + User.create {}, ()-> test.ok "saved" + +it "should trigger after create", (test)-> + User.afterCreate = ()-> + User.afterCreate = null + test.done() + User.create {}, ()-> test.ok "saved" + +it "should trigger before save", (test)-> + User.beforeSave = ()-> + User.beforeSave = null + test.done() + user = new User + user.save ()-> test.ok "saved" + +it "should trigger after save", (test)-> + User.afterSave = ()-> + User.afterSave = null + test.done() + user = new User + user.save ()-> test.ok "saved" + +it "should trigger before update", (test)-> + User.beforeUpdate = ()-> + User.beforeUpdate = null + test.done() + User.create {}, (err, user)-> + user.updateAttributes {email:"1@1.com"}, ()-> test.ok "updated" + +it "should trigger after update", (test)-> + User.afterUpdate = ()-> + User.afterUpdate = null + test.done() + User.create {}, (err, user)-> + user.updateAttributes {email:"1@1.com"}, ()-> test.ok "updated" + +it "should trigger before destroy", (test)-> + User.beforeDestroy = ()-> + User.beforeDestroy = null + test.done() + User.create {}, (err, user)-> + user.destroy() + +it "should trigger after destroy", (test)-> + User.afterDestroy = ()-> + User.afterDestroy = null + test.done() + User.create {}, (err, user)-> + user.destroy()