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

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 = { ...@@ -53,7 +53,11 @@ var hookTypes = {
beforeFind: {params: 1}, beforeFind: {params: 1},
beforeFindAfterExpandIncludeAll: {params: 1}, beforeFindAfterExpandIncludeAll: {params: 1},
beforeFindAfterOptions: {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 = { var hookAliases = {
beforeDelete: 'beforeDestroy', beforeDelete: 'beforeDestroy',
...@@ -91,13 +95,25 @@ Hooks.runHooks = function(hooks) { ...@@ -91,13 +95,25 @@ Hooks.runHooks = function(hooks) {
if (typeof hooks === 'string') { if (typeof hooks === 'string') {
hookType = hooks; 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)) { if (!Array.isArray(hooks)) {
hooks = hooks === undefined ? [] : [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) { var promise = Promise.each(hooks, function (hook) {
if (typeof hook === 'object') { if (typeof hook === 'object') {
hook = hook.fn; hook = hook.fn;
...@@ -330,3 +346,39 @@ Hooks.beforeFindAfterOptions = function(name, fn) { ...@@ -330,3 +346,39 @@ Hooks.beforeFindAfterOptions = function(name, fn) {
Hooks.afterFind = function(name, fn) { Hooks.afterFind = function(name, fn) {
return Hooks.addHook.call(this, 'afterFind', 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') ...@@ -9,8 +9,10 @@ var Utils = require('./utils')
, sql = require('sql') , sql = require('sql')
, SqlString = require('./sql-string') , SqlString = require('./sql-string')
, Transaction = require('./transaction') , Transaction = require('./transaction')
, Promise = require("./promise") , Promise = require('./promise')
, QueryTypes = require('./query-types'); , QueryTypes = require('./query-types')
, Hooks = require('./hooks')
, associationsMixin = require('./associations/mixin');
module.exports = (function() { module.exports = (function() {
/** /**
...@@ -38,10 +40,7 @@ module.exports = (function() { ...@@ -38,10 +40,7 @@ module.exports = (function() {
schemaDelimiter: '', schemaDelimiter: '',
defaultScope: null, defaultScope: null,
scopes: null, scopes: null,
hooks: { hooks: {}
beforeCreate: [],
afterCreate: []
}
}, options || {}); }, options || {});
this.associations = {}; this.associations = {};
...@@ -2008,8 +2007,8 @@ module.exports = (function() { ...@@ -2008,8 +2007,8 @@ module.exports = (function() {
}); });
}; };
Utils._.extend(Model.prototype, require('./associations/mixin')); Utils._.extend(Model.prototype, associationsMixin);
Utils._.extend(Model.prototype, require(__dirname + '/hooks')); Utils._.extend(Model.prototype, Hooks);
return Model; return Model;
})(); })();
...@@ -11,6 +11,7 @@ var url = require('url') ...@@ -11,6 +11,7 @@ var url = require('url')
, QueryTypes = require('./query-types') , QueryTypes = require('./query-types')
, sequelizeErrors = require('./errors') , sequelizeErrors = require('./errors')
, Promise = require('./promise') , Promise = require('./promise')
, Hooks = require('./hooks')
, deprecatedSeen = {} , deprecatedSeen = {}
, deprecated = function(message) { , deprecated = function(message) {
if (deprecatedSeen[message]) return; if (deprecatedSeen[message]) return;
...@@ -121,6 +122,12 @@ module.exports = (function() { ...@@ -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({ this.options = Utils._.extend({
dialect: 'mysql', dialect: 'mysql',
dialectModulePath: null, dialectModulePath: null,
...@@ -136,7 +143,8 @@ module.exports = (function() { ...@@ -136,7 +143,8 @@ module.exports = (function() {
replication: false, replication: false,
ssl: undefined, ssl: undefined,
pool: {}, pool: {},
quoteIdentifiers: true quoteIdentifiers: true,
hooks: {}
}, options || {}); }, options || {});
if (this.options.dialect === 'postgresql') { if (this.options.dialect === 'postgresql') {
...@@ -152,6 +160,8 @@ module.exports = (function() { ...@@ -152,6 +160,8 @@ module.exports = (function() {
this.options.logging = console.log; this.options.logging = console.log;
} }
this.options.hooks = this.replaceHookAliases(this.options.hooks);
this.config = { this.config = {
database: database, database: database,
username: username, username: username,
...@@ -185,8 +195,12 @@ module.exports = (function() { ...@@ -185,8 +195,12 @@ module.exports = (function() {
this.connectionManager = this.dialect.connectionManager; this.connectionManager = this.dialect.connectionManager;
this.importCache = {}; 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. * 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 * @property Utils
...@@ -231,6 +245,13 @@ module.exports = (function() { ...@@ -231,6 +245,13 @@ module.exports = (function() {
Sequelize.prototype.Transaction = Sequelize.Transaction = Transaction; 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 * A general error class
* @property Error * @property Error
* @see {Errors#BaseError} * @see {Errors#BaseError}
...@@ -452,9 +473,17 @@ module.exports = (function() { ...@@ -452,9 +473,17 @@ module.exports = (function() {
} }
options.sequelize = this; options.sequelize = this;
options.modelName = modelName;
this.runHooks('beforeDefine', attributes, options);
modelName = options.modelName;
delete options.modelName;
var factory = new Model(modelName, attributes, options); var factory = new Model(modelName, attributes, options);
this.modelManager.addDAO(factory.init(this.modelManager)); this.modelManager.addDAO(factory.init(this.modelManager));
this.runHooks('afterDefine', factory);
return factory; return factory;
}; };
......
...@@ -4322,6 +4322,100 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -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('aliases', function() {
describe('direct method', function() { describe('direct method', function() {
describe('#delete', 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!