不要怂,就是干,撸起袖子干!

Commit 0a537b2a by Mick Hansen

Merge pull request #2320 from overlookmotel/universal-hooks

Universal hooks for all models + hooks on Sequelize#define and Sequelize()
2 parents 2644b19f c8683217
......@@ -53,7 +53,11 @@ var hookTypes = {
beforeFind: {params: 1},
beforeFindAfterExpandIncludeAll: {params: 1},
beforeFindAfterOptions: {params: 1},
afterFind: {params: 2}
afterFind: {params: 2},
beforeDefine: {params: 2, sync: true},
afterDefine: {params: 1, sync: true},
beforeInit: {params: 2, sync: true},
afterInit: {params: 1, sync: true}
};
var hookAliases = {
beforeDelete: 'beforeDestroy',
......@@ -91,13 +95,25 @@ Hooks.runHooks = function(hooks) {
if (typeof hooks === 'string') {
hookType = hooks;
hooks = this.options.hooks[hooks] || [];
hooks = this.options.hooks[hookType] || [];
if (!Array.isArray(hooks)) hooks = hooks === undefined ? [] : [hooks];
if (this.sequelize) hooks = hooks.concat(this.sequelize.options.hooks[hookType] || []);
}
if (!Array.isArray(hooks)) {
hooks = hooks === undefined ? [] : [hooks];
}
// run hooks as sync functions if flagged as sync
if (hookTypes[hookType] && hookTypes[hookType].sync) {
hooks.forEach(function(hook) {
if (typeof hook === 'object') hook = hook.fn;
return hook.apply(self, fnArgs);
});
return;
}
// run hooks async
var promise = Promise.each(hooks, function (hook) {
if (typeof hook === 'object') {
hook = hook.fn;
......@@ -330,3 +346,39 @@ Hooks.beforeFindAfterOptions = function(name, fn) {
Hooks.afterFind = function(name, fn) {
return Hooks.addHook.call(this, 'afterFind', name, fn);
};
/**
* A hook that is run before a define call
* @param {String} name
* @param {Function} fn A callback function that is called with attributes, options, callback(err)
*/
Hooks.beforeDefine = function(name, fn) {
return Hooks.addHook.call(this, 'beforeDefine', name, fn);
};
/**
* A hook that is run after a define call
* @param {String} name
* @param {Function} fn A callback function that is called with factory, callback(err)
*/
Hooks.afterDefine = function(name, fn) {
return Hooks.addHook.call(this, 'afterDefine', name, fn);
};
/**
* A hook that is run before Sequelize() call
* @param {String} name
* @param {Function} fn A callback function that is called with config, options, callback(err)
*/
Hooks.beforeInit = function(name, fn) {
return Hooks.addHook.call(this, 'beforeInit', name, fn);
};
/**
* A hook that is run after Sequelize() call
* @param {String} name
* @param {Function} fn A callback function that is called with sequelize, callback(err)
*/
Hooks.afterInit = function(name, fn) {
return Hooks.addHook.call(this, 'afterInit', name, fn);
};
......@@ -9,8 +9,10 @@ var Utils = require('./utils')
, sql = require('sql')
, SqlString = require('./sql-string')
, Transaction = require('./transaction')
, Promise = require("./promise")
, QueryTypes = require('./query-types');
, Promise = require('./promise')
, QueryTypes = require('./query-types')
, Hooks = require('./hooks')
, associationsMixin = require('./associations/mixin');
module.exports = (function() {
/**
......@@ -38,10 +40,7 @@ module.exports = (function() {
schemaDelimiter: '',
defaultScope: null,
scopes: null,
hooks: {
beforeCreate: [],
afterCreate: []
}
hooks: {}
}, options || {});
this.associations = {};
......@@ -2008,8 +2007,8 @@ module.exports = (function() {
});
};
Utils._.extend(Model.prototype, require('./associations/mixin'));
Utils._.extend(Model.prototype, require(__dirname + '/hooks'));
Utils._.extend(Model.prototype, associationsMixin);
Utils._.extend(Model.prototype, Hooks);
return Model;
})();
......@@ -11,6 +11,7 @@ var url = require('url')
, QueryTypes = require('./query-types')
, sequelizeErrors = require('./errors')
, Promise = require('./promise')
, Hooks = require('./hooks')
, deprecatedSeen = {}
, deprecated = function(message) {
if (deprecatedSeen[message]) return;
......@@ -121,6 +122,12 @@ module.exports = (function() {
}
}
var config = {database: database, username: username, password: password};
Sequelize.runHooks('beforeInit', config, options);
database = config.database;
username = config.username;
password = config.password;
this.options = Utils._.extend({
dialect: 'mysql',
dialectModulePath: null,
......@@ -136,7 +143,8 @@ module.exports = (function() {
replication: false,
ssl: undefined,
pool: {},
quoteIdentifiers: true
quoteIdentifiers: true,
hooks: {}
}, options || {});
if (this.options.dialect === 'postgresql') {
......@@ -152,6 +160,8 @@ module.exports = (function() {
this.options.logging = console.log;
}
this.options.hooks = this.replaceHookAliases(this.options.hooks);
this.config = {
database: database,
username: username,
......@@ -185,8 +195,12 @@ module.exports = (function() {
this.connectionManager = this.dialect.connectionManager;
this.importCache = {};
Sequelize.runHooks('afterInit', this);
};
Sequelize.options = {hooks: {}};
/**
* A reference to sequelize utilities. Most users will not need to use these utils directly. However, you might want to use `Sequelize.Utils._`, which is a reference to the lodash library, if you don't already have it imported in your project.
* @property Utils
......@@ -231,6 +245,13 @@ module.exports = (function() {
Sequelize.prototype.Transaction = Sequelize.Transaction = Transaction;
/**
* Allow hooks to be defined on Sequelize + on sequelize instance as universal hooks to run on all models
* and on Sequelize/sequelize methods e.g. Sequelize(), Sequelize#define()
*/
Utils._.extend(Sequelize, Hooks);
Utils._.extend(Sequelize.prototype, Hooks);
/**
* A general error class
* @property Error
* @see {Errors#BaseError}
......@@ -452,9 +473,17 @@ module.exports = (function() {
}
options.sequelize = this;
options.modelName = modelName;
this.runHooks('beforeDefine', attributes, options);
modelName = options.modelName;
delete options.modelName;
var factory = new Model(modelName, attributes, options);
this.modelManager.addDAO(factory.init(this.modelManager));
this.runHooks('afterDefine', factory);
return factory;
};
......
......@@ -4322,6 +4322,100 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
});
});
describe('#define', function() {
before(function() {
this.sequelize.addHook('beforeDefine', function(attributes, options) {
options.modelName = 'bar';
options.name.plural = 'barrs';
attributes.type = DataTypes.STRING;
});
this.sequelize.addHook('afterDefine', function(factory) {
factory.options.name.singular = 'barr';
});
this.model = this.sequelize.define('foo', {name: DataTypes.STRING});
});
it('beforeDefine hook can change model name', function() {
expect(this.model.name).to.equal('bar');
});
it('beforeDefine hook can alter options', function() {
expect(this.model.options.name.plural).to.equal('barrs');
});
it('beforeDefine hook can alter attributes', function() {
expect(this.model.rawAttributes.type).to.be.ok;
});
it('afterDefine hook can alter options', function() {
expect(this.model.options.name.singular).to.equal('barr');
});
after(function() {
this.sequelize.options.hooks = {};
this.sequelize.modelManager.removeDAO(this.model);
});
});
describe('#init', function() {
before(function() {
Sequelize.addHook('beforeInit', function(config, options) {
config.database = 'db2';
options.host = 'server9';
});
Sequelize.addHook('afterInit', function(sequelize) {
sequelize.options.protocol = 'udp';
});
this.seq = new Sequelize('db', 'user', 'pass', {});
});
it('beforeInit hook can alter config', function() {
expect(this.seq.config.database).to.equal('db2');
});
it('beforeInit hook can alter options', function() {
expect(this.seq.options.host).to.equal('server9');
});
it('afterInit hook can alter options', function() {
expect(this.seq.options.protocol).to.equal('udp');
});
after(function() {
Sequelize.options.hooks = {};
});
});
describe('universal', function() {
beforeEach(function() {
this.sequelize.addHook('beforeFind', function(options) {
options.where.name = 'Chong';
});
this.Person = this.sequelize.define('Person', {name: DataTypes.STRING});
return this.Person.sync({ force: true }).bind(this).then(function() {
return this.Person.create({name: 'Cheech'});
}).then(function() {
return this.Person.create({name: 'Chong'});
});
});
it('hooks run on all models', function() {
return this.Person.find({where: {name: 'Cheech'}}).then(function(person) {
expect(person.name).to.equal('Chong');
});
});
afterEach(function() {
this.sequelize.options.hooks = {};
});
});
describe('aliases', function() {
describe('direct method', function() {
describe('#delete', function() {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!