Added active record style callbacks and hooks. Before and after create, save, update, destroy and after initialization.

This commit is contained in:
Julien Guimont 2011-11-16 23:24:43 -05:00
parent 13bfb1c972
commit 39bbece115
3 changed files with 188 additions and 56 deletions

View File

@ -2,12 +2,14 @@
* Module deps * Module deps
*/ */
var Validatable = require('./validatable').Validatable; var Validatable = require('./validatable').Validatable;
var Hookable = require('./hookable').Hookable;
var util = require('util'); var util = require('util');
var jutil = require('./jutil'); var jutil = require('./jutil');
exports.AbstractClass = AbstractClass; exports.AbstractClass = AbstractClass;
jutil.inherits(AbstractClass, Validatable); jutil.inherits(AbstractClass, Validatable);
jutil.inherits(AbstractClass, Hookable);
/** /**
* Abstract class constructor * Abstract class constructor
@ -79,6 +81,8 @@ function AbstractClass(data) {
return null; return null;
} }
} }
this.trigger("initialize");
}; };
/** /**
@ -107,21 +111,24 @@ AbstractClass.create = function (data) {
obj = new this(data); obj = new this(data);
// validation required // validation required
if (!obj.isValid()) { obj.trigger("validation", function(){
return callback(new Error('Validation error'), obj); if (!obj.isValid()) {
} return callback(new Error('Validation error'), obj);
}
});
} }
this.schema.adapter.create(modelName, data, function (err, id) { obj.trigger("create", function(){
if (id) { this._adapter().create(modelName, data, function (err, id) {
defineReadonlyProp(obj, 'id', id); if (id) {
this.cache[id] = obj; defineReadonlyProp(obj, 'id', id);
} this.constructor.cache[id] = obj;
if (callback) { }
callback(err, obj); if (callback) {
} callback(err, obj);
}.bind(this)); }
}.bind(this));
});
}; };
AbstractClass.exists = function exists(id, cb) { AbstractClass.exists = function exists(id, cb) {
@ -213,29 +220,36 @@ AbstractClass.prototype.save = function (options, callback) {
if (!('throws' in options)) { if (!('throws' in options)) {
options.throws = false; options.throws = false;
} }
if (options.validate && !this.isValid()) { this.trigger("validation", function(){
var err = new Error('Validation error'); if (options.validate && !this.isValid()) {
if (options.throws) { var err = new Error('Validation error');
throw err; if (options.throws) {
throw err;
}
return callback && callback(err);
} }
return callback && callback(err); });
}
var modelName = this.constructor.modelName; this.trigger("save", function(){
var data = this.toObject(true); var modelName = this.constructor.modelName;
if (this.id) { var data = this.toObject(true);
this._adapter().save(modelName, data, function (err) { if (this.id) {
if (err) { this.trigger("update", function(){
console.log(err); this._adapter().save(modelName, data, function (err) {
} else { if (err) {
this.constructor.call(this, data); console.log(err);
} } else {
if (callback) { this.constructor.call(this, data);
callback(err, this); }
} if (callback) {
}.bind(this)); callback(err, this);
} else { }
this.constructor.create(this, callback); }.bind(this));
} });
} else {
this.constructor.create(this, callback);
}
});
}; };
AbstractClass.prototype.isNewRecord = function () { AbstractClass.prototype.isNewRecord = function () {
@ -262,10 +276,12 @@ AbstractClass.prototype.toObject = function (onlySchema) {
}; };
AbstractClass.prototype.destroy = function (cb) { AbstractClass.prototype.destroy = function (cb) {
this._adapter().destroy(this.constructor.modelName, this.id, function (err) { this.trigger("destroy", function(){
delete this.constructor.cache[this.id]; this._adapter().destroy(this.constructor.modelName, this.id, function (err) {
cb && cb(err); delete this.constructor.cache[this.id];
}.bind(this)); cb && cb(err);
}.bind(this));
});
}; };
AbstractClass.prototype.updateAttribute = function (name, value, cb) { AbstractClass.prototype.updateAttribute = function (name, value, cb) {
@ -279,24 +295,29 @@ AbstractClass.prototype.updateAttributes = function updateAttributes(data, cb) {
Object.keys(data).forEach(function (key) { Object.keys(data).forEach(function (key) {
this[key] = data[key]; this[key] = data[key];
}.bind(this)); }.bind(this));
if (!this.isValid()) { this.trigger("validation", function(){
var err = new Error('Validation error'); if (!this.isValid()) {
return cb && cb(err); 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));
} }
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));
});
}; };
/** /**

32
lib/hookable.js Normal file
View File

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

79
test/hookable_test.coffee Normal file
View File

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