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

Commit 6ef449c1 by Simon Schick Committed by GitHub

refactor(hooks): remove method aliases for hooks (#10880)

* refactor(hooks): remove method aliases for hooks

BREAKING CHANGE:

In order to streamline API:

- All method style add hook functions have been removed in favor of a composition based approach.
- Hook names have been removed, you can add and remove them by function reference instead which was supported before.
- Another notable change that `this` inside of hooks no longer refers to the the the hook subject, it should not be used.

This affects `Model`, `Sequelize` and `Transaction`.

#### Composition

Before: `MyModel.beforeCreate(...)`
After: `MyModel.hooks.add('beforeCreate', ...)`

Before: `MyModel.addHook('beforeCreate', ...)`
After: `MyModel.hooks.add('beforeCreate', ...)`

Before: `MyModel.removeHook('beforeCreate', ...)`
After: `MyModel.hooks.remove('beforeCreate', ...)`

Before: `transaction.afterCommit(...)`
After: `transaction.hooks.add('afterCommit', ...)`

#### Names

Before:

```js
MyModel.addHook('beforeCreate', 'named', fn);
MyModel.removeHook('beforeCreate', 'named');
```

After:

```js
MyModel.hooks.add('beforeCreate', fn);
MyModel.hooks.remove('beforeCreate', fn);
```

#### Scope

Before: `MyModel.addHook('beforeCreate', function() { this.someMethod(); });`
After: `MyModel.hooks.add('beforeCreate', () => { MyModel.someMethod(); });`
1 parent 73b1246a
......@@ -42,7 +42,7 @@ script:
jobs:
include:
- stage: lint
node_js: '6'
node_js: '10'
script:
- npm run lint
- npm run lint-docs
......
......@@ -68,25 +68,14 @@ User.init({
sequelize
});
// Method 2 via the .addHook() method
User.addHook('beforeValidate', (user, options) => {
// Method 2 via the .hooks.add() method
User.hooks.add('beforeValidate', (user, options) => {
user.mood = 'happy';
});
User.addHook('afterValidate', 'someCustomName', (user, options) => {
User.hooks.add('afterValidate', (user, options) => {
return Promise.reject(new Error("I'm afraid I can't let you do that!"));
});
// Method 3 via the direct method
User.beforeCreate((user, options) => {
return hashPassword(user.password).then(hashedPw => {
user.password = hashedPw;
});
});
User.afterValidate('myHookAfter', (user, options) => {
user.username = 'Toni';
});
```
## Removing hooks
......@@ -99,14 +88,16 @@ Book.init({
title: DataTypes.STRING
}, { sequelize });
Book.addHook('afterCreate', 'notifyUsers', (book, options) => {
// ...
});
function notifyUsers(book, options) {
}
Book.hooks.add('afterCreate', notifyUsers);
Book.removeHook('afterCreate', 'notifyUsers');
Book.hooks.remove('afterCreate', notifyUsers);
```
You can have many hooks with same name. Calling `.removeHook()` will remove all of them.
You can have many hooks with same name. Calling `.hooks.remove(()` will remove all of them.
## Global / universal hooks
......@@ -145,10 +136,10 @@ User.create() // Runs the global hook
Project.create() // Runs its own hook (because the global hook is overwritten)
```
### Permanent Hooks (Sequelize.addHook)
### Permanent Hooks (Sequelize.hooks.add)
```js
sequelize.addHook('beforeCreate', () => {
sequelize.hooks.add('beforeCreate', () => {
// Do stuff
});
```
......@@ -198,7 +189,7 @@ These hooks can be useful if you need to asynchronously obtain database credenti
For example, we can asynchronously obtain a database password from a rotating token store, and mutate Sequelize's configuration object with the new credentials:
```js
sequelize.beforeConnect((config) => {
sequelize.hooks.add('beforeConnect', (config) => {
return getAuthToken()
.then((token) => {
config.password = token;
......@@ -221,7 +212,7 @@ afterCreate / afterUpdate / afterSave / afterDestroy
```js
// ...define ...
User.beforeCreate(user => {
User.hooks.add('beforeCreate', user => {
if (user.accessLevel > 10 && user.username !== "Boss") {
throw new Error("You can't grant this user an access level above 10!")
}
......@@ -273,7 +264,7 @@ The `options` argument of hook method would be the second argument provided to t
cloned and extended version.
```js
Model.beforeBulkCreate((records, {fields}) => {
Model.hooks.add('beforeBulkCreate', (records, {fields}) => {
// records = the first argument sent to .bulkCreate
// fields = one of the second argument fields sent to .bulkCreate
})
......@@ -284,14 +275,14 @@ Model.bulkCreate([
], {fields: ['username']} // options parameter
)
Model.beforeBulkUpdate(({attributes, where}) => {
Model.hooks.add('beforeBulkUpdate', ({attributes, where}) => {
// where - in one of the fields of the clone of second argument sent to .update
// attributes - is one of the fields that the clone of second argument of .update would be extended with
})
Model.update({gender: 'Male'} /*attributes argument*/, { where: {username: 'Tom'}} /*where argument*/)
Model.beforeBulkDestroy(({where, individualHooks}) => {
Model.hooks.add('beforeBulkDestroy', ({where, individualHooks}) => {
// individualHooks - default of overridden value of extended clone of second argument sent to Model.destroy
// where - in one of the fields of the clone of second argument sent to Model.destroy
})
......@@ -310,7 +301,7 @@ Users.bulkCreate([
updateOnDuplicate: ['isMember']
});
User.beforeBulkCreate((users, options) => {
User.hooks.add('beforeBulkCreate', (users, options) => {
for (const user of users) {
if (user.isMember) {
user.memberSince = new Date();
......@@ -364,7 +355,7 @@ Note that many model operations in Sequelize allow you to specify a transaction
```js
// Here we use the promise-style of async hooks rather than
// the callback.
User.addHook('afterCreate', (user, options) => {
User.hooks.add('afterCreate', (user, options) => {
// 'transaction' will be available in options.transaction
// This operation will be part of the same transaction as the
......
......@@ -178,13 +178,13 @@ An `afterCommit` hook can be added to both managed and unmanaged transaction obj
```js
sequelize.transaction(t => {
t.afterCommit((transaction) => {
t.hooks.add('afterCommit', (transaction) => {
// Your logic
});
});
sequelize.transaction().then(t => {
t.afterCommit((transaction) => {
t.hooks.add('afterCommit', (transaction) => {
// Your logic
});
......@@ -203,11 +203,11 @@ You can use the `afterCommit` hook in conjunction with model hooks to know when
of a transaction
```js
model.afterSave((instance, options) => {
model.hooks.add('afterSave', (instance, options) => {
if (options.transaction) {
// Save done within a transaction, wait until transaction is committed to
// notify listeners the instance has been saved
options.transaction.afterCommit(() => /* Notify */)
options.transaction.hooks.add('afterCommit', () => /* Notify */)
return;
}
// Save done outside a transaction, safe for callers to fetch the updated model
......
......@@ -6,7 +6,7 @@ Sequelize v6 is the next major release after v4
### Support for Node 8 and up
Sequelize v6 will only support Node 8 and up
Sequelize v6 will only support Node 8 and up.
### Removed support for `operatorAliases`
......@@ -57,3 +57,48 @@ db.transaction(async transaction => {
}, { transaction });
});
```
### Refactored hooks
In order to streamline API:
- All method style add hook functions have been removed in favor of a composition based approach.
- Hook names have been removed, you can add and remove them by function reference instead which was supported before.
- Another notable change that `this` inside of hooks no longer refers to the the the hook subject, it should not be used.
This affects `Model`, `Sequelize` and `Transaction`.
#### Composition
Before: `MyModel.beforeCreate(...)`
After: `MyModel.hooks.add('beforeCreate', ...)`
Before: `MyModel.addHook('beforeCreate', ...)`
After: `MyModel.hooks.add('beforeCreate', ...)`
Before: `MyModel.removeHook('beforeCreate', ...)`
After: `MyModel.hooks.remove('beforeCreate', ...)`
Before: `transaction.afterCommit(...)`
After: `transaction.hooks.add('afterCommit', ...)`
#### Names
Before:
```js
MyModel.addHook('beforeCreate', 'named', fn);
MyModel.removeHook('beforeCreate', 'named');
```
After:
```js
MyModel.hooks.add('beforeCreate', fn);
MyModel.hooks.remove('beforeCreate', fn);
```
#### Scope
Before: `MyModel.addHook('beforeCreate', function() { this.someMethod(); });`
After: `MyModel.hooks.add('beforeCreate', () => { MyModel.someMethod(); });`
......@@ -27,7 +27,7 @@ const Mixin = {
options = Object.assign(options, _.omit(source.options, ['hooks']));
if (options.useHooks) {
this.runHooks('beforeAssociate', { source, target, type: HasMany }, options);
this.hooks.run('beforeAssociate', { source, target, type: HasMany }, options);
}
// the id is in the foreign table or in a connecting table
......@@ -38,7 +38,7 @@ const Mixin = {
association.mixin(source.prototype);
if (options.useHooks) {
this.runHooks('afterAssociate', { source, target, type: HasMany, association }, options);
this.hooks.run('afterAssociate', { source, target, type: HasMany, association }, options);
}
return association;
......@@ -58,7 +58,7 @@ const Mixin = {
options = Object.assign(options, _.omit(source.options, ['hooks', 'timestamps', 'scopes', 'defaultScope']));
if (options.useHooks) {
this.runHooks('beforeAssociate', { source, target, type: BelongsToMany }, options);
this.hooks.run('beforeAssociate', { source, target, type: BelongsToMany }, options);
}
// the id is in the foreign table or in a connecting table
const association = new BelongsToMany(source, target, options);
......@@ -68,7 +68,7 @@ const Mixin = {
association.mixin(source.prototype);
if (options.useHooks) {
this.runHooks('afterAssociate', { source, target, type: BelongsToMany, association }, options);
this.hooks.run('afterAssociate', { source, target, type: BelongsToMany, association }, options);
}
return association;
......@@ -99,7 +99,7 @@ function singleLinked(Type) {
options.useHooks = options.hooks;
if (options.useHooks) {
source.runHooks('beforeAssociate', { source, target, type: Type }, options);
source.hooks.run('beforeAssociate', { source, target, type: Type }, options);
}
// the id is in the foreign table
const association = new Type(source, target, Object.assign(options, source.options));
......@@ -109,7 +109,7 @@ function singleLinked(Type) {
association.mixin(source.prototype);
if (options.useHooks) {
source.runHooks('afterAssociate', { source, target, type: Type, association }, options);
source.hooks.run('afterAssociate', { source, target, type: Type, association }, options);
}
return association;
......
......@@ -325,9 +325,9 @@ class ConnectionManager {
* @returns {Promise<Connection>}
*/
_connect(config) {
return this.sequelize.runHooks('beforeConnect', config)
return this.sequelize.hooks.run('beforeConnect', config)
.then(() => this.dialect.connectionManager.connect(config))
.then(connection => this.sequelize.runHooks('afterConnect', connection, config).return(connection));
.then(connection => this.sequelize.hooks.run('afterConnect', connection, config).return(connection));
}
/**
......
......@@ -102,14 +102,14 @@ class InstanceValidator {
* @private
*/
_validateAndRunHooks() {
const runHooks = this.modelInstance.constructor.runHooks.bind(this.modelInstance.constructor);
return runHooks('beforeValidate', this.modelInstance, this.options)
const { hooks } = this.modelInstance.constructor;
return hooks.run('beforeValidate', this.modelInstance, this.options)
.then(() =>
this._validate()
.catch(error => runHooks('validationFailed', this.modelInstance, this.options, error)
.catch(error => hooks.run('validationFailed', this.modelInstance, this.options, error)
.then(newError => { throw newError || error; }))
)
.then(() => runHooks('afterValidate', this.modelInstance, this.options))
.then(() => hooks.run('afterValidate', this.modelInstance, this.options))
.return(this.modelInstance);
}
......
......@@ -15,7 +15,7 @@ const Promise = require('./promise');
const Association = require('./associations/base');
const HasMany = require('./associations/has-many');
const DataTypes = require('./data-types');
const Hooks = require('./hooks');
const { Hooks } = require('./hooks');
const associationsMixin = require('./associations/mixin');
const Op = require('./operators');
const { noDoubleNestedGroup } = require('./utils/deprecations');
......@@ -939,7 +939,7 @@ class Model {
schema: globalOptions.schema
}, options);
this.sequelize.runHooks('beforeDefine', attributes, options);
this.sequelize.hooks.run('beforeDefine', attributes, options);
if (options.modelName !== this.name) {
Object.defineProperty(this, 'name', { value: options.modelName });
......@@ -967,7 +967,6 @@ class Model {
}
this.associations = {};
this._setupHooks(options.hooks);
this.underscored = this.options.underscored;
......@@ -1050,7 +1049,9 @@ class Model {
this._scopeNames = ['defaultScope'];
this.sequelize.modelManager.addModel(this);
this.sequelize.runHooks('afterDefine', this);
this.sequelize.hooks.run('afterDefine', this);
this.hooks = new Hooks(this.options.hooks, this.sequelize.hooks);
return this;
}
......@@ -1275,7 +1276,7 @@ class Model {
return Promise.try(() => {
if (options.hooks) {
return this.runHooks('beforeSync', options);
return this.hooks.run('beforeSync', options);
}
}).then(() => {
if (options.force) {
......@@ -1361,7 +1362,7 @@ class Model {
));
}).then(() => {
if (options.hooks) {
return this.runHooks('afterSync', options);
return this.hooks.run('afterSync', options);
}
}).return(this);
}
......@@ -1699,7 +1700,7 @@ class Model {
this._injectScope(options);
if (options.hooks) {
return this.runHooks('beforeFind', options);
return this.hooks.run('beforeFind', options);
}
}).then(() => {
this._conformIncludes(options, this);
......@@ -1707,7 +1708,7 @@ class Model {
this._expandIncludeAll(options);
if (options.hooks) {
return this.runHooks('beforeFindAfterExpandIncludeAll', options);
return this.hooks.run('beforeFindAfterExpandIncludeAll', options);
}
}).then(() => {
options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes);
......@@ -1742,7 +1743,7 @@ class Model {
options = this._paranoidClause(this, options);
if (options.hooks) {
return this.runHooks('beforeFindAfterOptions', options);
return this.hooks.run('beforeFindAfterOptions', options);
}
}).then(() => {
originalOptions = Utils.cloneDeep(options);
......@@ -1750,7 +1751,7 @@ class Model {
return this.QueryInterface.select(this, this.getTableName(options), options);
}).tap(results => {
if (options.hooks) {
return this.runHooks('afterFind', results, options);
return this.hooks.run('afterFind', results, options);
}
}).then(results => {
......@@ -2019,7 +2020,7 @@ class Model {
...options
};
if (options.hooks) {
return this.runHooks('beforeCount', options);
return this.hooks.run('beforeCount', options);
}
}).then(() => {
let col = options.col || '*';
......@@ -2463,7 +2464,7 @@ class Model {
return Promise.try(() => {
if (options.hooks) {
return this.runHooks('beforeUpsert', values, options);
return this.hooks.run('beforeUpsert', values, options);
}
})
.then(() => {
......@@ -2478,7 +2479,7 @@ class Model {
})
.tap(result => {
if (options.hooks) {
return this.runHooks('afterUpsert', result, options);
return this.hooks.run('afterUpsert', result, options);
}
});
});
......@@ -2554,7 +2555,7 @@ class Model {
return Promise.try(() => {
// Run before hook
if (options.hooks) {
return this.runHooks('beforeBulkCreate', instances, options);
return this.hooks.run('beforeBulkCreate', instances, options);
}
}).then(() => {
// Validate
......@@ -2658,7 +2659,7 @@ class Model {
// Run after hook
if (options.hooks) {
return this.runHooks('afterBulkCreate', instances, options);
return this.hooks.run('afterBulkCreate', instances, options);
}
}).then(() => instances);
}
......@@ -2734,13 +2735,13 @@ class Model {
return Promise.try(() => {
// Run before hook
if (options.hooks) {
return this.runHooks('beforeBulkDestroy', options);
return this.hooks.run('beforeBulkDestroy', options);
}
}).then(() => {
// Get daos and run beforeDestroy hook on each record individually
if (options.individualHooks) {
return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark })
.map(instance => this.runHooks('beforeDestroy', instance, options).then(() => instance))
.map(instance => this.hooks.run('beforeDestroy', instance, options).then(() => instance))
.then(_instances => {
instances = _instances;
});
......@@ -2766,12 +2767,12 @@ class Model {
}).tap(() => {
// Run afterDestroy hook on each record individually
if (options.individualHooks) {
return Promise.map(instances, instance => this.runHooks('afterDestroy', instance, options));
return Promise.map(instances, instance => this.hooks.run('afterDestroy', instance, options));
}
}).tap(() => {
// Run after hook
if (options.hooks) {
return this.runHooks('afterBulkDestroy', options);
return this.hooks.run('afterBulkDestroy', options);
}
});
}
......@@ -2808,13 +2809,13 @@ class Model {
return Promise.try(() => {
// Run before hook
if (options.hooks) {
return this.runHooks('beforeBulkRestore', options);
return this.hooks.run('beforeBulkRestore', options);
}
}).then(() => {
// Get daos and run beforeRestore hook on each record individually
if (options.individualHooks) {
return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark, paranoid: false })
.map(instance => this.runHooks('beforeRestore', instance, options).then(() => instance))
.map(instance => this.hooks.run('beforeRestore', instance, options).then(() => instance))
.then(_instances => {
instances = _instances;
});
......@@ -2832,12 +2833,12 @@ class Model {
}).tap(() => {
// Run afterDestroy hook on each record individually
if (options.individualHooks) {
return Promise.map(instances, instance => this.runHooks('afterRestore', instance, options));
return Promise.map(instances, instance => this.hooks.run('afterRestore', instance, options));
}
}).tap(() => {
// Run after hook
if (options.hooks) {
return this.runHooks('afterBulkRestore', options);
return this.hooks.run('afterBulkRestore', options);
}
});
}
......@@ -2935,7 +2936,7 @@ class Model {
// Run before hook
if (options.hooks) {
options.attributes = values;
return this.runHooks('beforeBulkUpdate', options).then(() => {
return this.hooks.run('beforeBulkUpdate', options).then(() => {
values = options.attributes;
delete options.attributes;
});
......@@ -2974,7 +2975,7 @@ class Model {
});
// Run beforeUpdate hook
return this.runHooks('beforeUpdate', instance, options).then(() => {
return this.hooks.run('beforeUpdate', instance, options).then(() => {
if (!different) {
const thisChangedValues = {};
_.forIn(instance.dataValues, (newValue, attr) => {
......@@ -3050,7 +3051,7 @@ class Model {
}).tap(result => {
if (options.individualHooks) {
return Promise.map(instances, instance => {
return this.runHooks('afterUpdate', instance, options);
return this.hooks.run('afterUpdate', instance, options);
}).then(() => {
result[1] = instances;
});
......@@ -3059,7 +3060,7 @@ class Model {
// Run after hook
if (options.hooks) {
options.attributes = values;
return this.runHooks('afterBulkUpdate', options).then(() => {
return this.hooks.run('afterBulkUpdate', options).then(() => {
delete options.attributes;
});
}
......@@ -3729,7 +3730,7 @@ class Model {
ignoreChanged = _.without(ignoreChanged, updatedAtAttr);
}
return this.constructor.runHooks(`before${hook}`, this, options)
return this.constructor.hooks.run(`before${hook}`, this, options)
.then(() => {
if (options.defaultFields && !this.isNewRecord) {
afterHookValues = _.pick(this.dataValues, _.difference(this.changed(), ignoreChanged));
......@@ -3884,7 +3885,7 @@ class Model {
.tap(result => {
// Run after hook
if (options.hooks) {
return this.constructor.runHooks(`after${hook}`, result, options);
return this.constructor.hooks.run(`after${hook}`, result, options);
}
})
.then(result => {
......@@ -4012,7 +4013,7 @@ class Model {
return Promise.try(() => {
// Run before hook
if (options.hooks) {
return this.constructor.runHooks('beforeDestroy', this, options);
return this.constructor.hooks.run('beforeDestroy', this, options);
}
}).then(() => {
const where = this.where(true);
......@@ -4044,7 +4045,7 @@ class Model {
}).tap(() => {
// Run after hook
if (options.hooks) {
return this.constructor.runHooks('afterDestroy', this, options);
return this.constructor.hooks.run('afterDestroy', this, options);
}
});
}
......@@ -4089,7 +4090,7 @@ class Model {
return Promise.try(() => {
// Run before hook
if (options.hooks) {
return this.constructor.runHooks('beforeRestore', this, options);
return this.constructor.hooks.run('beforeRestore', this, options);
}
}).then(() => {
const deletedAtCol = this.constructor._timestampAttributes.deletedAt;
......@@ -4101,7 +4102,7 @@ class Model {
}).tap(() => {
// Run after hook
if (options.hooks) {
return this.constructor.runHooks('afterRestore', this, options);
return this.constructor.hooks.run('afterRestore', this, options);
}
});
}
......@@ -4338,6 +4339,5 @@ class Model {
}
Object.assign(Model, associationsMixin);
Hooks.applyTo(Model, true);
module.exports = Model;
......@@ -17,7 +17,7 @@ const TableHints = require('./table-hints');
const IndexHints = require('./index-hints');
const sequelizeErrors = require('./errors');
const Promise = require('./promise');
const Hooks = require('./hooks');
const { Hooks } = require('./hooks');
const Association = require('./associations/index');
const Validator = require('./utils/validator-extras').validator;
const Op = require('./operators');
......@@ -218,8 +218,9 @@ class Sequelize {
options = options || {};
config = { database, username, password };
}
this.hooks = new Hooks(options.hooks);
Sequelize.runHooks('beforeInit', config, options);
Sequelize.hooks.run('beforeInit', config, options);
this.options = Object.assign({
dialect: null,
......@@ -273,7 +274,6 @@ class Sequelize {
this.options.logging = console.log;
}
this._setupHooks(options.hooks);
this.config = {
database: config.database || this.options.database,
......@@ -333,7 +333,7 @@ class Sequelize {
this.importCache = {};
Sequelize.runHooks('afterInit', this);
Sequelize.hooks.run('afterInit', this);
}
/**
......@@ -624,9 +624,9 @@ class Sequelize {
: this.connectionManager.getConnection(options);
}).then(connection => {
const query = new this.dialect.Query(connection, this, options);
return this.runHooks('beforeQuery', options, query)
return this.hooks.run('beforeQuery', options, query)
.then(() => query.run(sql, bindParameters))
.finally(() => this.runHooks('afterQuery', options, query))
.finally(() => this.hooks.run('afterQuery', options, query))
.finally(() => {
if (!options.transaction) {
return this.connectionManager.releaseConnection(connection);
......@@ -779,7 +779,7 @@ class Sequelize {
return Promise.try(() => {
if (options.hooks) {
return this.runHooks('beforeBulkSync', options);
return this.hooks.run('beforeBulkSync', options);
}
}).then(() => {
if (options.force) {
......@@ -804,7 +804,7 @@ class Sequelize {
return Promise.each(models, model => model.sync(options));
}).then(() => {
if (options.hooks) {
return this.runHooks('afterBulkSync', options);
return this.hooks.run('afterBulkSync', options);
}
}).return(this);
}
......@@ -1177,6 +1177,8 @@ class Sequelize {
}
}
Sequelize.hooks = new Hooks();
// Aliases
Sequelize.prototype.fn = Sequelize.fn;
Sequelize.prototype.col = Sequelize.col;
......@@ -1193,8 +1195,6 @@ Sequelize.prototype.validate = Sequelize.prototype.authenticate;
*/
Sequelize.version = require('../package.json').version;
Sequelize.options = { hooks: {} };
/**
* @private
*/
......@@ -1275,13 +1275,6 @@ Sequelize.prototype.Association = Sequelize.Association = Association;
Sequelize.useInflection = Utils.useInflection;
/**
* 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()
*/
Hooks.applyTo(Sequelize);
Hooks.applyTo(Sequelize.prototype);
/**
* Expose various errors available
*/
......
'use strict';
const Promise = require('./promise');
const { Hooks } = require('./hooks');
/**
* The transaction object is used to identify a running transaction.
......@@ -21,9 +22,9 @@ class Transaction {
* @param {string} options.deferrable Sets the constraints to be deferred or immediately checked.
*/
constructor(sequelize, options = {}) {
this.hooks = new Hooks();
this.sequelize = sequelize;
this.savepoints = [];
this._afterCommitHooks = [];
// get dialect specific transaction options
const generateTransactionId = this.sequelize.dialect.QueryGenerator.generateTransactionId;
......@@ -68,11 +69,7 @@ class Transaction {
return this.cleanup();
}
return null;
}).tap(
() => Promise.each(
this._afterCommitHooks,
hook => Promise.resolve(hook.apply(this, [this])))
);
}).tap(() => this.hooks.run('afterCommit', this));
}
/**
......@@ -159,20 +156,6 @@ class Transaction {
}
/**
* A hook that is run after a transaction is committed
*
* @param {Function} fn A callback function that is called with the committed transaction
* @name afterCommit
* @memberof Sequelize.Transaction
*/
afterCommit(fn) {
if (!fn || typeof fn !== 'function') {
throw new Error('"fn" must be a function');
}
this._afterCommitHooks.push(fn);
}
/**
* Types can be set per-transaction by passing `options.type` to `sequelize.transaction`.
* Default to `DEFERRED` but you can override the default type by passing `options.transactionType` in `new Sequelize`.
* Sqlite only.
......
......@@ -39,9 +39,9 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeBulk = sinon.spy(),
afterBulk = sinon.spy();
this.User.beforeBulkCreate(beforeBulk);
this.User.hooks.add('beforeBulkCreate', beforeBulk);
this.User.afterBulkCreate(afterBulk);
this.User.hooks.add('afterBulkCreate', afterBulk);
return this.User.bulkCreate([
{ username: 'Cheech', mood: 'sad' },
......@@ -55,7 +55,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
describe('on error', () => {
it('should return an error from before', function() {
this.User.beforeBulkCreate(() => {
this.User.hooks.add('beforeBulkCreate', () => {
throw new Error('Whoops!');
});
......@@ -66,7 +66,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('should return an error from after', function() {
this.User.afterBulkCreate(() => {
this.User.hooks.add('afterBulkCreate', () => {
throw new Error('Whoops!');
});
......@@ -101,22 +101,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
let beforeBulkCreate = false,
afterBulkCreate = false;
this.User.beforeBulkCreate(() => {
this.User.hooks.add('beforeBulkCreate', () => {
beforeBulkCreate = true;
return Promise.resolve();
});
this.User.afterBulkCreate(() => {
this.User.hooks.add('afterBulkCreate', () => {
afterBulkCreate = true;
return Promise.resolve();
});
this.User.beforeCreate(user => {
this.User.hooks.add('beforeCreate', user => {
user.beforeHookTest = true;
return Promise.resolve();
});
this.User.afterCreate(user => {
this.User.hooks.add('afterCreate', user => {
user.username = `User${user.id}`;
return Promise.resolve();
});
......@@ -135,21 +135,21 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
let beforeBulkCreate = false,
afterBulkCreate = false;
this.User.beforeBulkCreate(() => {
this.User.hooks.add('beforeBulkCreate', () => {
beforeBulkCreate = true;
return Promise.resolve();
});
this.User.afterBulkCreate(() => {
this.User.hooks.add('afterBulkCreate', () => {
afterBulkCreate = true;
return Promise.resolve();
});
this.User.beforeCreate(() => {
this.User.hooks.add('beforeCreate', () => {
return Promise.reject(new Error('You shall not pass!'));
});
this.User.afterCreate(user => {
this.User.hooks.add('afterCreate', user => {
user.username = `User${user.id}`;
return Promise.resolve();
});
......@@ -169,8 +169,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeBulk = sinon.spy(),
afterBulk = sinon.spy();
this.User.beforeBulkUpdate(beforeBulk);
this.User.afterBulkUpdate(afterBulk);
this.User.hooks.add('beforeBulkUpdate', beforeBulk);
this.User.hooks.add('afterBulkUpdate', afterBulk);
return this.User.bulkCreate([
{ username: 'Cheech', mood: 'sad' },
......@@ -186,7 +186,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
describe('on error', () => {
it('should return an error from before', function() {
this.User.beforeBulkUpdate(() => {
this.User.hooks.add('beforeBulkUpdate', () => {
throw new Error('Whoops!');
});
......@@ -199,7 +199,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('should return an error from after', function() {
this.User.afterBulkUpdate(() => {
this.User.hooks.add('afterBulkUpdate', () => {
throw new Error('Whoops!');
});
......@@ -236,16 +236,16 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeBulk = sinon.spy(),
afterBulk = sinon.spy();
this.User.beforeBulkUpdate(beforeBulk);
this.User.hooks.add('beforeBulkUpdate', beforeBulk);
this.User.afterBulkUpdate(afterBulk);
this.User.hooks.add('afterBulkUpdate', afterBulk);
this.User.beforeUpdate(user => {
this.User.hooks.add('beforeUpdate', user => {
expect(user.changed()).to.not.be.empty;
user.beforeHookTest = true;
});
this.User.afterUpdate(user => {
this.User.hooks.add('afterUpdate', user => {
user.username = `User${user.id}`;
});
......@@ -264,7 +264,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('should run the after/before functions for each item created successfully changing some data before updating', function() {
this.User.beforeUpdate(user => {
this.User.hooks.add('beforeUpdate', user => {
expect(user.changed()).to.not.be.empty;
if (user.get('id') === 1) {
user.set('aNumber', user.get('aNumber') + 3);
......@@ -286,15 +286,15 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeBulk = sinon.spy(),
afterBulk = sinon.spy();
this.User.beforeBulkUpdate(beforeBulk);
this.User.hooks.add('beforeBulkUpdate', beforeBulk);
this.User.afterBulkUpdate(afterBulk);
this.User.hooks.add('afterBulkUpdate', afterBulk);
this.User.beforeUpdate(() => {
this.User.hooks.add('beforeUpdate', () => {
throw new Error('You shall not pass!');
});
this.User.afterUpdate(user => {
this.User.hooks.add('afterUpdate', user => {
user.username = `User${user.id}`;
});
......@@ -316,8 +316,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeBulk = sinon.spy(),
afterBulk = sinon.spy();
this.User.beforeBulkDestroy(beforeBulk);
this.User.afterBulkDestroy(afterBulk);
this.User.hooks.add('beforeBulkDestroy', beforeBulk);
this.User.hooks.add('afterBulkDestroy', afterBulk);
return this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } }).then(() => {
expect(beforeBulk).to.have.been.calledOnce;
......@@ -328,7 +328,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
describe('on error', () => {
it('should return an error from before', function() {
this.User.beforeBulkDestroy(() => {
this.User.hooks.add('beforeBulkDestroy', () => {
throw new Error('Whoops!');
});
......@@ -336,7 +336,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('should return an error from after', function() {
this.User.afterBulkDestroy(() => {
this.User.hooks.add('afterBulkDestroy', () => {
throw new Error('Whoops!');
});
......@@ -370,22 +370,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
beforeHook = false,
afterHook = false;
this.User.beforeBulkDestroy(() => {
this.User.hooks.add('beforeBulkDestroy', () => {
beforeBulk = true;
return Promise.resolve();
});
this.User.afterBulkDestroy(() => {
this.User.hooks.add('afterBulkDestroy', () => {
afterBulk = true;
return Promise.resolve();
});
this.User.beforeDestroy(() => {
this.User.hooks.add('beforeDestroy', () => {
beforeHook = true;
return Promise.resolve();
});
this.User.afterDestroy(() => {
this.User.hooks.add('afterDestroy', () => {
afterHook = true;
return Promise.resolve();
});
......@@ -408,22 +408,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
beforeHook = false,
afterHook = false;
this.User.beforeBulkDestroy(() => {
this.User.hooks.add('beforeBulkDestroy', () => {
beforeBulk = true;
return Promise.resolve();
});
this.User.afterBulkDestroy(() => {
this.User.hooks.add('afterBulkDestroy', () => {
afterBulk = true;
return Promise.resolve();
});
this.User.beforeDestroy(() => {
this.User.hooks.add('beforeDestroy', () => {
beforeHook = true;
return Promise.reject(new Error('You shall not pass!'));
});
this.User.afterDestroy(() => {
this.User.hooks.add('afterDestroy', () => {
afterHook = true;
return Promise.resolve();
});
......@@ -456,8 +456,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeBulk = sinon.spy(),
afterBulk = sinon.spy();
this.ParanoidUser.beforeBulkRestore(beforeBulk);
this.ParanoidUser.afterBulkRestore(afterBulk);
this.ParanoidUser.hooks.add('beforeBulkRestore', beforeBulk);
this.ParanoidUser.hooks.add('afterBulkRestore', afterBulk);
return this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } }).then(() => {
expect(beforeBulk).to.have.been.calledOnce;
......@@ -468,7 +468,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
describe('on error', () => {
it('should return an error from before', function() {
this.ParanoidUser.beforeBulkRestore(() => {
this.ParanoidUser.hooks.add('beforeBulkRestore', () => {
throw new Error('Whoops!');
});
......@@ -476,7 +476,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('should return an error from after', function() {
this.ParanoidUser.afterBulkRestore(() => {
this.ParanoidUser.hooks.add('afterBulkRestore', () => {
throw new Error('Whoops!');
});
......@@ -504,10 +504,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.ParanoidUser.beforeBulkRestore(beforeBulk);
this.ParanoidUser.afterBulkRestore(afterBulk);
this.ParanoidUser.beforeRestore(beforeHook);
this.ParanoidUser.afterRestore(afterHook);
this.ParanoidUser.hooks.add('beforeBulkRestore', beforeBulk);
this.ParanoidUser.hooks.add('afterBulkRestore', afterBulk);
this.ParanoidUser.hooks.add('beforeRestore', beforeHook);
this.ParanoidUser.hooks.add('afterRestore', afterHook);
return this.ParanoidUser.bulkCreate([
{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }
......@@ -529,14 +529,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.ParanoidUser.beforeBulkRestore(beforeBulk);
this.ParanoidUser.afterBulkRestore(afterBulk);
this.ParanoidUser.beforeRestore(() => {
this.ParanoidUser.hooks.add('beforeBulkRestore', beforeBulk);
this.ParanoidUser.hooks.add('afterBulkRestore', afterBulk);
this.ParanoidUser.hooks.add('beforeRestore', () => {
beforeHook();
return Promise.reject(new Error('You shall not pass!'));
});
this.ParanoidUser.afterRestore(afterHook);
this.ParanoidUser.hooks.add('afterRestore', afterHook);
return this.ParanoidUser.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }).then(() => {
return this.ParanoidUser.destroy({ where: { aNumber: 1 } });
......
......@@ -33,7 +33,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('hook runs', function() {
let beforeHook = false;
this.User.beforeCount(() => {
this.User.hooks.add('beforeCount', () => {
beforeHook = true;
});
......@@ -44,7 +44,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('beforeCount hook can change options', function() {
this.User.beforeCount(options => {
this.User.hooks.add('beforeCount', options => {
options.where.username = 'adam';
});
......@@ -54,7 +54,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
describe('on error', () => {
it('in beforeCount hook returns error', function() {
this.User.beforeCount(() => {
this.User.hooks.add('beforeCount', () => {
throw new Error('Oops!');
});
......
......@@ -31,10 +31,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
beforeSave = sinon.spy(),
afterSave = sinon.spy();
this.User.beforeCreate(beforeHook);
this.User.afterCreate(afterHook);
this.User.beforeSave(beforeSave);
this.User.afterSave(afterSave);
this.User.hooks.add('beforeCreate', beforeHook);
this.User.hooks.add('afterCreate', afterHook);
this.User.hooks.add('beforeSave', beforeSave);
this.User.hooks.add('afterSave', afterSave);
return this.User.create({ username: 'Toni', mood: 'happy' }).then(() => {
expect(beforeHook).to.have.been.calledOnce;
......@@ -52,13 +52,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
afterHook = sinon.spy(),
afterSave = sinon.spy();
this.User.beforeCreate(() => {
this.User.hooks.add('beforeCreate', () => {
beforeHook();
throw new Error('Whoops!');
});
this.User.afterCreate(afterHook);
this.User.beforeSave(beforeSave);
this.User.afterSave(afterSave);
this.User.hooks.add('afterCreate', afterHook);
this.User.hooks.add('beforeSave', beforeSave);
this.User.hooks.add('afterSave', afterSave);
return expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => {
expect(beforeHook).to.have.been.calledOnce;
......@@ -75,13 +75,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
afterSave = sinon.spy();
this.User.beforeCreate(beforeHook);
this.User.afterCreate(() => {
this.User.hooks.add('beforeCreate', beforeHook);
this.User.hooks.add('afterCreate', () => {
afterHook();
throw new Error('Whoops!');
});
this.User.beforeSave(beforeSave);
this.User.afterSave(afterSave);
this.User.hooks.add('beforeSave', beforeSave);
this.User.hooks.add('afterSave', afterSave);
return expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => {
expect(beforeHook).to.have.been.calledOnce;
......@@ -102,7 +102,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
let hookCalled = 0;
A.addHook('afterCreate', () => {
A.hooks.add('afterCreate', () => {
hookCalled++;
return Promise.resolve();
});
......@@ -126,7 +126,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('beforeValidate', function() {
let hookCalled = 0;
this.User.beforeValidate(user => {
this.User.hooks.add('beforeValidate', user => {
user.mood = 'happy';
hookCalled++;
});
......@@ -141,7 +141,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('afterValidate', function() {
let hookCalled = 0;
this.User.afterValidate(user => {
this.User.hooks.add('afterValidate', user => {
user.mood = 'neutral';
hookCalled++;
});
......@@ -156,7 +156,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('beforeCreate', function() {
let hookCalled = 0;
this.User.beforeCreate(user => {
this.User.hooks.add('beforeCreate', user => {
user.mood = 'happy';
hookCalled++;
});
......@@ -171,7 +171,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('beforeSave', function() {
let hookCalled = 0;
this.User.beforeSave(user => {
this.User.hooks.add('beforeSave', user => {
user.mood = 'happy';
hookCalled++;
});
......@@ -186,12 +186,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('beforeSave with beforeCreate', function() {
let hookCalled = 0;
this.User.beforeCreate(user => {
this.User.hooks.add('beforeCreate', user => {
user.mood = 'sad';
hookCalled++;
});
this.User.beforeSave(user => {
this.User.hooks.add('beforeSave', user => {
user.mood = 'happy';
hookCalled++;
});
......
......@@ -27,8 +27,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.User.beforeDestroy(beforeHook);
this.User.afterDestroy(afterHook);
this.User.hooks.add('beforeDestroy', beforeHook);
this.User.hooks.add('afterDestroy', afterHook);
return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => {
return user.destroy().then(() => {
......@@ -44,11 +44,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.User.beforeDestroy(() => {
this.User.hooks.add('beforeDestroy', () => {
beforeHook();
throw new Error('Whoops!');
});
this.User.afterDestroy(afterHook);
this.User.hooks.add('afterDestroy', afterHook);
return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => {
return expect(user.destroy()).to.be.rejected.then(() => {
......@@ -62,8 +62,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.User.beforeDestroy(beforeHook);
this.User.afterDestroy(() => {
this.User.hooks.add('beforeDestroy', beforeHook);
this.User.hooks.add('afterDestroy', () => {
afterHook();
throw new Error('Whoops!');
});
......@@ -107,7 +107,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('should not throw error when a beforeDestroy hook changes a virtual column', function() {
this.ParanoidUser.beforeDestroy(instance => instance.virtualField = 2);
this.ParanoidUser.hooks.add('beforeDestroy', instance => instance.virtualField = 2);
return this.ParanoidUser.sync({ force: true })
.then(() => this.ParanoidUser.create({ username: 'user1' }))
......
......@@ -30,7 +30,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('allow changing attributes via beforeFind #5675', function() {
this.User.beforeFind(options => {
this.User.hooks.add('beforeFind', options => {
options.attributes = {
include: ['id']
};
......@@ -45,19 +45,19 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
beforeHook3 = false,
afterHook = false;
this.User.beforeFind(() => {
this.User.hooks.add('beforeFind', () => {
beforeHook = true;
});
this.User.beforeFindAfterExpandIncludeAll(() => {
this.User.hooks.add('beforeFindAfterExpandIncludeAll', () => {
beforeHook2 = true;
});
this.User.beforeFindAfterOptions(() => {
this.User.hooks.add('beforeFindAfterOptions', () => {
beforeHook3 = true;
});
this.User.afterFind(() => {
this.User.hooks.add('afterFind', () => {
afterHook = true;
});
......@@ -71,7 +71,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('beforeFind hook can change options', function() {
this.User.beforeFind(options => {
this.User.hooks.add('beforeFind', options => {
options.where.username = 'joe';
});
......@@ -81,7 +81,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('beforeFindAfterExpandIncludeAll hook can change options', function() {
this.User.beforeFindAfterExpandIncludeAll(options => {
this.User.hooks.add('beforeFindAfterExpandIncludeAll', options => {
options.where.username = 'joe';
});
......@@ -91,7 +91,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('beforeFindAfterOptions hook can change options', function() {
this.User.beforeFindAfterOptions(options => {
this.User.hooks.add('beforeFindAfterOptions', options => {
options.where.username = 'joe';
});
......@@ -101,7 +101,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('afterFind hook can change results', function() {
this.User.afterFind(user => {
this.User.hooks.add('afterFind', user => {
user.mood = 'sad';
});
......@@ -113,7 +113,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
describe('on error', () => {
it('in beforeFind hook returns error', function() {
this.User.beforeFind(() => {
this.User.hooks.add('beforeFind', () => {
throw new Error('Oops!');
});
......@@ -123,7 +123,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('in beforeFindAfterExpandIncludeAll hook returns error', function() {
this.User.beforeFindAfterExpandIncludeAll(() => {
this.User.hooks.add('beforeFindAfterExpandIncludeAll', () => {
throw new Error('Oops!');
});
......@@ -133,7 +133,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('in beforeFindAfterOptions hook returns error', function() {
this.User.beforeFindAfterOptions(() => {
this.User.hooks.add('beforeFindAfterOptions', () => {
throw new Error('Oops!');
});
......@@ -143,7 +143,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
it('in afterFind hook returns error', function() {
this.User.afterFind(() => {
this.User.hooks.add('afterFind', () => {
throw new Error('Oops!');
});
......
......@@ -37,13 +37,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
describe('#define', () => {
before(function() {
this.sequelize.addHook('beforeDefine', (attributes, options) => {
this.sequelize.hooks.add('beforeDefine', (attributes, options) => {
options.modelName = 'bar';
options.name.plural = 'barrs';
attributes.type = DataTypes.STRING;
});
this.sequelize.addHook('afterDefine', factory => {
this.sequelize.hooks.add('afterDefine', factory => {
factory.options.name.singular = 'barr';
});
......@@ -67,19 +67,19 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
after(function() {
this.sequelize.options.hooks = {};
this.sequelize.hooks.removeAll();
this.sequelize.modelManager.removeModel(this.model);
});
});
describe('#init', () => {
before(function() {
Sequelize.addHook('beforeInit', (config, options) => {
Sequelize.hooks.add('beforeInit', (config, options) => {
config.database = 'db2';
options.host = 'server9';
});
Sequelize.addHook('afterInit', sequelize => {
Sequelize.hooks.add('afterInit', sequelize => {
sequelize.options.protocol = 'udp';
});
......@@ -99,7 +99,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
after(() => {
Sequelize.options.hooks = {};
Sequelize.hooks.removeAll();
});
});
......@@ -236,8 +236,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.User.beforeSync(beforeHook);
this.User.afterSync(afterHook);
this.User.hooks.add('beforeSync', beforeHook);
this.User.hooks.add('afterSync', afterHook);
return this.User.sync().then(() => {
expect(beforeHook).to.have.been.calledOnce;
......@@ -249,8 +249,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.User.beforeSync(beforeHook);
this.User.afterSync(afterHook);
this.User.hooks.add('beforeSync', beforeHook);
this.User.hooks.add('afterSync', afterHook);
return this.User.sync({ hooks: false }).then(() => {
expect(beforeHook).to.not.have.been.called;
......@@ -265,11 +265,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.User.beforeSync(() => {
this.User.hooks.add('beforeSync', () => {
beforeHook();
throw new Error('Whoops!');
});
this.User.afterSync(afterHook);
this.User.hooks.add('afterSync', afterHook);
return expect(this.User.sync()).to.be.rejected.then(() => {
expect(beforeHook).to.have.been.calledOnce;
......@@ -281,8 +281,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.User.beforeSync(beforeHook);
this.User.afterSync(() => {
this.User.hooks.add('beforeSync', beforeHook);
this.User.hooks.add('afterSync', () => {
afterHook();
throw new Error('Whoops!');
});
......@@ -303,10 +303,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
modelBeforeHook = sinon.spy(),
modelAfterHook = sinon.spy();
this.sequelize.beforeBulkSync(beforeHook);
this.User.beforeSync(modelBeforeHook);
this.User.afterSync(modelAfterHook);
this.sequelize.afterBulkSync(afterHook);
this.sequelize.hooks.add('beforeBulkSync', beforeHook);
this.User.hooks.add('beforeSync', modelBeforeHook);
this.User.hooks.add('afterSync', modelAfterHook);
this.sequelize.hooks.add('afterBulkSync', afterHook);
return this.sequelize.sync().then(() => {
expect(beforeHook).to.have.been.calledOnce;
......@@ -322,10 +322,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
modelBeforeHook = sinon.spy(),
modelAfterHook = sinon.spy();
this.sequelize.beforeBulkSync(beforeHook);
this.User.beforeSync(modelBeforeHook);
this.User.afterSync(modelAfterHook);
this.sequelize.afterBulkSync(afterHook);
this.sequelize.hooks.add('beforeBulkSync', beforeHook);
this.User.hooks.add('beforeSync', modelBeforeHook);
this.User.hooks.add('afterSync', modelAfterHook);
this.sequelize.hooks.add('afterBulkSync', afterHook);
return this.sequelize.sync({ hooks: false }).then(() => {
expect(beforeHook).to.not.have.been.called;
......@@ -336,7 +336,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
afterEach(function() {
this.sequelize.options.hooks = {};
this.sequelize.hooks.removeAll();
});
});
......@@ -346,11 +346,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('should return an error from before', function() {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.sequelize.beforeBulkSync(() => {
this.sequelize.hooks.add('beforeBulkSync', () => {
beforeHook();
throw new Error('Whoops!');
});
this.sequelize.afterBulkSync(afterHook);
this.sequelize.hooks.add('afterBulkSync', afterHook);
return expect(this.sequelize.sync()).to.be.rejected.then(() => {
expect(beforeHook).to.have.been.calledOnce;
......@@ -362,8 +362,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.sequelize.beforeBulkSync(beforeHook);
this.sequelize.afterBulkSync(() => {
this.sequelize.hooks.add('beforeBulkSync', beforeHook);
this.sequelize.hooks.add('afterBulkSync', () => {
afterHook();
throw new Error('Whoops!');
});
......@@ -375,42 +375,24 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
afterEach(function() {
this.sequelize.options.hooks = {};
this.sequelize.hooks.removeAll();
});
});
});
describe('#removal', () => {
it('should be able to remove by name', function() {
const sasukeHook = sinon.spy(),
narutoHook = sinon.spy();
this.User.addHook('beforeCreate', 'sasuke', sasukeHook);
this.User.addHook('beforeCreate', 'naruto', narutoHook);
return this.User.create({ username: 'makunouchi' }).then(() => {
expect(sasukeHook).to.have.been.calledOnce;
expect(narutoHook).to.have.been.calledOnce;
this.User.removeHook('beforeCreate', 'sasuke');
return this.User.create({ username: 'sendo' });
}).then(() => {
expect(sasukeHook).to.have.been.calledOnce;
expect(narutoHook).to.have.been.calledTwice;
});
});
it('should be able to remove by reference', function() {
const sasukeHook = sinon.spy(),
narutoHook = sinon.spy();
this.User.addHook('beforeCreate', sasukeHook);
this.User.addHook('beforeCreate', narutoHook);
this.User.hooks.add('beforeCreate', sasukeHook);
this.User.hooks.add('beforeCreate', narutoHook);
return this.User.create({ username: 'makunouchi' }).then(() => {
expect(sasukeHook).to.have.been.calledOnce;
expect(narutoHook).to.have.been.calledOnce;
this.User.removeHook('beforeCreate', sasukeHook);
this.User.hooks.remove('beforeCreate', sasukeHook);
return this.User.create({ username: 'sendo' });
}).then(() => {
expect(sasukeHook).to.have.been.calledOnce;
......@@ -422,13 +404,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const sasukeHook = sinon.spy(),
narutoHook = sinon.spy();
this.User.addHook('beforeSave', sasukeHook);
this.User.addHook('beforeSave', narutoHook);
this.User.hooks.add('beforeSave', sasukeHook);
this.User.hooks.add('beforeSave', narutoHook);
return this.User.create({ username: 'makunouchi' }).then(user => {
expect(sasukeHook).to.have.been.calledOnce;
expect(narutoHook).to.have.been.calledOnce;
this.User.removeHook('beforeSave', sasukeHook);
this.User.hooks.remove('beforeSave', sasukeHook);
return user.update({ username: 'sendo' });
}).then(() => {
expect(sasukeHook).to.have.been.calledOnce;
......
......@@ -38,8 +38,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.ParanoidUser.beforeRestore(beforeHook);
this.ParanoidUser.afterRestore(afterHook);
this.ParanoidUser.hooks.add('beforeRestore', beforeHook);
this.ParanoidUser.hooks.add('afterRestore', afterHook);
return this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }).then(user => {
return user.destroy().then(() => {
......@@ -57,11 +57,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.ParanoidUser.beforeRestore(() => {
this.ParanoidUser.hooks.add('beforeRestore', () => {
beforeHook();
throw new Error('Whoops!');
});
this.ParanoidUser.afterRestore(afterHook);
this.ParanoidUser.hooks.add('afterRestore', afterHook);
return this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }).then(user => {
return user.destroy().then(() => {
......@@ -77,8 +77,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.ParanoidUser.beforeRestore(beforeHook);
this.ParanoidUser.afterRestore(() => {
this.ParanoidUser.hooks.add('beforeRestore', beforeHook);
this.ParanoidUser.hooks.add('afterRestore', () => {
afterHook();
throw new Error('Whoops!');
});
......
......@@ -29,10 +29,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
beforeSave = sinon.spy(),
afterSave = sinon.spy();
this.User.beforeUpdate(beforeHook);
this.User.afterUpdate(afterHook);
this.User.beforeSave(beforeSave);
this.User.afterSave(afterSave);
this.User.hooks.add('beforeUpdate', beforeHook);
this.User.hooks.add('afterUpdate', afterHook);
this.User.hooks.add('beforeSave', beforeSave);
this.User.hooks.add('afterSave', afterSave);
return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => {
return user.update({ username: 'Chong' }).then(user => {
......@@ -53,13 +53,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
beforeSave = sinon.spy(),
afterSave = sinon.spy();
this.User.beforeUpdate(() => {
this.User.hooks.add('beforeUpdate', () => {
beforeHook();
throw new Error('Whoops!');
});
this.User.afterUpdate(afterHook);
this.User.beforeSave(beforeSave);
this.User.afterSave(afterSave);
this.User.hooks.add('afterUpdate', afterHook);
this.User.hooks.add('beforeSave', beforeSave);
this.User.hooks.add('afterSave', afterSave);
return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => {
return expect(user.update({ username: 'Chong' })).to.be.rejected.then(() => {
......@@ -77,13 +77,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
beforeSave = sinon.spy(),
afterSave = sinon.spy();
this.User.beforeUpdate(beforeHook);
this.User.afterUpdate(() => {
this.User.hooks.add('beforeUpdate', beforeHook);
this.User.hooks.add('afterUpdate', () => {
afterHook();
throw new Error('Whoops!');
});
this.User.beforeSave(beforeSave);
this.User.afterSave(afterSave);
this.User.hooks.add('beforeSave', beforeSave);
this.User.hooks.add('afterSave', afterSave);
return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => {
return expect(user.update({ username: 'Chong' })).to.be.rejected.then(() => {
......@@ -99,7 +99,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
describe('preserves changes to instance', () => {
it('beforeValidate', function() {
this.User.beforeValidate(user => {
this.User.hooks.add('beforeValidate', user => {
user.mood = 'happy';
});
......@@ -113,7 +113,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('afterValidate', function() {
this.User.afterValidate(user => {
this.User.hooks.add('afterValidate', user => {
user.mood = 'sad';
});
......@@ -128,7 +128,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('beforeSave', function() {
let hookCalled = 0;
this.User.beforeSave(user => {
this.User.hooks.add('beforeSave', user => {
user.mood = 'happy';
hookCalled++;
});
......@@ -145,12 +145,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('beforeSave with beforeUpdate', function() {
let hookCalled = 0;
this.User.beforeUpdate(user => {
this.User.hooks.add('beforeUpdate', user => {
user.mood = 'sad';
hookCalled++;
});
this.User.beforeSave(user => {
this.User.hooks.add('beforeSave', user => {
user.mood = 'happy';
hookCalled++;
});
......
......@@ -29,8 +29,8 @@ if (Support.sequelize.dialect.supports.upserts) {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.User.beforeUpsert(beforeHook);
this.User.afterUpsert(afterHook);
this.User.hooks.add('beforeUpsert', beforeHook);
this.User.hooks.add('afterUpsert', afterHook);
return this.User.upsert({ username: 'Toni', mood: 'happy' }).then(() => {
expect(beforeHook).to.have.been.calledOnce;
......@@ -44,11 +44,11 @@ if (Support.sequelize.dialect.supports.upserts) {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.User.beforeUpsert(() => {
this.User.hooks.add('beforeUpsert', () => {
beforeHook();
throw new Error('Whoops!');
});
this.User.afterUpsert(afterHook);
this.User.hooks.add('afterUpsert', afterHook);
return expect(this.User.upsert({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => {
expect(beforeHook).to.have.been.calledOnce;
......@@ -60,8 +60,8 @@ if (Support.sequelize.dialect.supports.upserts) {
const beforeHook = sinon.spy(),
afterHook = sinon.spy();
this.User.beforeUpsert(beforeHook);
this.User.afterUpsert(() => {
this.User.hooks.add('beforeUpsert', beforeHook);
this.User.hooks.add('afterUpsert', () => {
afterHook();
throw new Error('Whoops!');
});
......@@ -78,7 +78,7 @@ if (Support.sequelize.dialect.supports.upserts) {
let hookCalled = 0;
const valuesOriginal = { mood: 'sad', username: 'leafninja' };
this.User.beforeUpsert(values => {
this.User.hooks.add('beforeUpsert', values => {
values.mood = 'happy';
hookCalled++;
});
......
......@@ -24,12 +24,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
describe('#validate', () => {
describe('#create', () => {
it('should return the user', function() {
this.User.beforeValidate(user => {
this.User.hooks.add('beforeValidate', user => {
user.username = 'Bob';
user.mood = 'happy';
});
this.User.afterValidate(user => {
this.User.hooks.add('afterValidate', user => {
user.username = 'Toni';
});
......@@ -42,7 +42,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
describe('#3534, hooks modifications', () => {
it('fields modified in hooks are saved', function() {
this.User.afterValidate(user => {
this.User.hooks.add('afterValidate', user => {
//if username is defined and has more than 5 char
user.username = user.username
? user.username.length < 5 ? null : user.username
......@@ -51,7 +51,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
});
this.User.beforeValidate(user => {
this.User.hooks.add('beforeValidate', user => {
user.mood = user.mood || 'neutral';
});
......@@ -108,7 +108,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
describe('on error', () => {
it('should emit an error from after hook', function() {
this.User.afterValidate(user => {
this.User.hooks.add('afterValidate', user => {
user.mood = 'ecstatic';
throw new Error('Whoops! Changed user.mood!');
});
......@@ -119,7 +119,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('should call validationFailed hook', function() {
const validationFailedHook = sinon.spy();
this.User.validationFailed(validationFailedHook);
this.User.hooks.add('validationFailed', validationFailedHook);
return expect(this.User.create({ mood: 'happy' })).to.be.rejected.then(() => {
expect(validationFailedHook).to.have.been.calledOnce;
......@@ -129,7 +129,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('should not replace the validation error in validationFailed hook by default', function() {
const validationFailedHook = sinon.stub();
this.User.validationFailed(validationFailedHook);
this.User.hooks.add('validationFailed', validationFailedHook);
return expect(this.User.create({ mood: 'happy' })).to.be.rejected.then(err => {
expect(err.name).to.equal('SequelizeValidationError');
......@@ -139,7 +139,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => {
it('should replace the validation error if validationFailed hook creates a new error', function() {
const validationFailedHook = sinon.stub().throws(new Error('Whoops!'));
this.User.validationFailed(validationFailedHook);
this.User.hooks.add('validationFailed', validationFailedHook);
return expect(this.User.create({ mood: 'happy' })).to.be.rejected.then(err => {
expect(err.message).to.equal('Whoops!');
......
......@@ -146,7 +146,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => {
email: DataTypes.STRING
});
User.beforeUpdate(instance => {
User.hooks.add('beforeUpdate', instance => {
instance.set('email', 'B');
});
......@@ -177,7 +177,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => {
email: DataTypes.STRING
});
User.beforeUpdate(instance => {
User.hooks.add('beforeUpdate', instance => {
instance.set('email', 'C');
});
......@@ -214,7 +214,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => {
}
});
User.beforeUpdate(instance => {
User.hooks.add('beforeUpdate', instance => {
instance.set('email', 'B');
});
......@@ -247,7 +247,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => {
}
});
User.beforeUpdate(instance => {
User.hooks.add('beforeUpdate', instance => {
instance.set('email', 'B');
});
......
......@@ -206,7 +206,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => {
email: DataTypes.STRING
});
User.beforeUpdate(instance => {
User.hooks.add('beforeUpdate', instance => {
instance.set('email', 'B');
});
......@@ -237,7 +237,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => {
email: DataTypes.STRING
});
User.beforeUpdate(instance => {
User.hooks.add('beforeUpdate', instance => {
instance.set('email', 'C');
});
......@@ -274,7 +274,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => {
}
});
User.beforeUpdate(instance => {
User.hooks.add('beforeUpdate', instance => {
instance.set('email', 'B');
});
......@@ -307,7 +307,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => {
}
});
User.beforeUpdate(instance => {
User.hooks.add('beforeUpdate', instance => {
instance.set('email', 'B');
});
......
......@@ -489,7 +489,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => {
});
let changed;
User.afterUpdate(instance => {
User.hooks.add('afterUpdate', instance => {
changed = instance.changed();
return;
});
......
......@@ -1142,7 +1142,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
});
it('should properly set data when individualHooks are true', function() {
this.User.beforeUpdate(instance => {
this.User.hooks.add('beforeUpdate', instance => {
instance.set('intVal', 1);
});
......
......@@ -454,7 +454,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
}
});
User.beforeCreate(instance => {
User.hooks.add('beforeCreate', instance => {
instance.set('username', instance.get('username').trim());
});
......
......@@ -5,10 +5,10 @@ const Support = require('../support');
const runningQueries = new Set();
before(function() {
this.sequelize.addHook('beforeQuery', (options, query) => {
this.sequelize.hooks.add('beforeQuery', (options, query) => {
runningQueries.add(query);
});
this.sequelize.addHook('afterQuery', (options, query) => {
this.sequelize.hooks.add('afterQuery', (options, query) => {
runningQueries.delete(query);
});
});
......
......@@ -86,7 +86,7 @@ if (current.dialect.supports.transactions) {
let transaction;
return expect(this.sequelize.transaction(t => {
transaction = t;
transaction.afterCommit(hook);
transaction.hooks.add('afterCommit', hook);
return this.sequelize.query('SELECT 1+1', { transaction, type: QueryTypes.SELECT });
}).then(() => {
expect(hook).to.have.been.calledOnce;
......@@ -98,7 +98,7 @@ if (current.dialect.supports.transactions) {
it('does not run hooks when a transaction is rolled back', function() {
const hook = sinon.spy();
return expect(this.sequelize.transaction(transaction => {
transaction.afterCommit(hook);
transaction.hooks.add('afterCommit', hook);
return Promise.reject(new Error('Rollback'));
})
).to.eventually.be.rejected.then(() => {
......@@ -217,7 +217,7 @@ if (current.dialect.supports.transactions) {
return expect(
this.sequelize.transaction().then(t => {
transaction = t;
transaction.afterCommit(hook);
transaction.hooks.add('afterCommit', hook);
return t.commit().then(() => {
expect(hook).to.have.been.calledOnce;
expect(hook).to.have.been.calledWith(t);
......@@ -239,7 +239,7 @@ if (current.dialect.supports.transactions) {
const hook = sinon.spy();
return expect(
this.sequelize.transaction().then(t => {
t.afterCommit(hook);
t.hooks.add('afterCommit', hook);
return t.rollback().then(() => {
expect(hook).to.not.have.been.called;
});
......@@ -247,69 +247,6 @@ if (current.dialect.supports.transactions) {
).to.eventually.be.fulfilled;
});
it('should throw an error if null is passed to afterCommit', function() {
const hook = null;
let transaction;
return expect(
this.sequelize.transaction().then(t => {
transaction = t;
transaction.afterCommit(hook);
return t.commit();
}).catch(err => {
// Cleanup this transaction so other tests don't
// fail due to an open transaction
if (!transaction.finished) {
return transaction.rollback().then(() => {
throw err;
});
}
throw err;
})
).to.eventually.be.rejectedWith('"fn" must be a function');
});
it('should throw an error if undefined is passed to afterCommit', function() {
const hook = undefined;
let transaction;
return expect(
this.sequelize.transaction().then(t => {
transaction = t;
transaction.afterCommit(hook);
return t.commit();
}).catch(err => {
// Cleanup this transaction so other tests don't
// fail due to an open transaction
if (!transaction.finished) {
return transaction.rollback().then(() => {
throw err;
});
}
throw err;
})
).to.eventually.be.rejectedWith('"fn" must be a function');
});
it('should throw an error if an object is passed to afterCommit', function() {
const hook = {};
let transaction;
return expect(
this.sequelize.transaction().then(t => {
transaction = t;
transaction.afterCommit(hook);
return t.commit();
}).catch(err => {
// Cleanup this transaction so other tests don't
// fail due to an open transaction
if (!transaction.finished) {
return transaction.rollback().then(() => {
throw err;
});
}
throw err;
})
).to.eventually.be.rejectedWith('"fn" must be a function');
});
it('does not allow commits after rollback', function() {
return expect(this.sequelize.transaction().then(t => {
return t.rollback().then(() => {
......
......@@ -663,7 +663,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => {
describe('beforeBelongsToManyAssociate', () => {
it('should trigger', function() {
const beforeAssociate = sinon.spy();
this.Projects.beforeAssociate(beforeAssociate);
this.Projects.hooks.add('beforeAssociate', beforeAssociate);
this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: true });
const beforeAssociateArgs = beforeAssociate.getCall(0).args;
......@@ -681,7 +681,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => {
});
it('should not trigger association hooks', function() {
const beforeAssociate = sinon.spy();
this.Projects.beforeAssociate(beforeAssociate);
this.Projects.hooks.add('beforeAssociate', beforeAssociate);
this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: false });
expect(beforeAssociate).to.not.have.been.called;
});
......@@ -689,7 +689,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => {
describe('afterBelongsToManyAssociate', () => {
it('should trigger', function() {
const afterAssociate = sinon.spy();
this.Projects.afterAssociate(afterAssociate);
this.Projects.hooks.add('afterAssociate', afterAssociate);
this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: true });
const afterAssociateArgs = afterAssociate.getCall(0).args;
......@@ -708,7 +708,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => {
});
it('should not trigger association hooks', function() {
const afterAssociate = sinon.spy();
this.Projects.afterAssociate(afterAssociate);
this.Projects.hooks.add('afterAssociate', afterAssociate);
this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: false });
expect(afterAssociate).to.not.have.been.called;
});
......
......@@ -59,7 +59,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => {
describe('beforeBelongsToAssociate', () => {
it('should trigger', function() {
const beforeAssociate = sinon.spy();
this.Projects.beforeAssociate(beforeAssociate);
this.Projects.hooks.add('beforeAssociate', beforeAssociate);
this.Projects.belongsTo(this.Tasks, { hooks: true });
const beforeAssociateArgs = beforeAssociate.getCall(0).args;
......@@ -77,7 +77,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => {
});
it('should not trigger association hooks', function() {
const beforeAssociate = sinon.spy();
this.Projects.beforeAssociate(beforeAssociate);
this.Projects.hooks.add('beforeAssociate', beforeAssociate);
this.Projects.belongsTo(this.Tasks, { hooks: false });
expect(beforeAssociate).to.not.have.been.called;
});
......@@ -85,7 +85,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => {
describe('afterBelongsToAssociate', () => {
it('should trigger', function() {
const afterAssociate = sinon.spy();
this.Projects.afterAssociate(afterAssociate);
this.Projects.hooks.add('afterAssociate', afterAssociate);
this.Projects.belongsTo(this.Tasks, { hooks: true });
const afterAssociateArgs = afterAssociate.getCall(0).args;
......@@ -105,7 +105,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => {
});
it('should not trigger association hooks', function() {
const afterAssociate = sinon.spy();
this.Projects.afterAssociate(afterAssociate);
this.Projects.hooks.add('afterAssociate', afterAssociate);
this.Projects.belongsTo(this.Tasks, { hooks: false });
expect(afterAssociate).to.not.have.been.called;
});
......
......@@ -217,7 +217,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => {
describe('beforeHasManyAssociate', () => {
it('should trigger', function() {
const beforeAssociate = sinon.spy();
this.Projects.beforeAssociate(beforeAssociate);
this.Projects.hooks.add('beforeAssociate', beforeAssociate);
this.Projects.hasMany(this.Tasks, { hooks: true });
const beforeAssociateArgs = beforeAssociate.getCall(0).args;
......@@ -234,7 +234,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => {
});
it('should not trigger association hooks', function() {
const beforeAssociate = sinon.spy();
this.Projects.beforeAssociate(beforeAssociate);
this.Projects.hooks.add('beforeAssociate', beforeAssociate);
this.Projects.hasMany(this.Tasks, { hooks: false });
expect(beforeAssociate).to.not.have.been.called;
});
......@@ -242,7 +242,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => {
describe('afterHasManyAssociate', () => {
it('should trigger', function() {
const afterAssociate = sinon.spy();
this.Projects.afterAssociate(afterAssociate);
this.Projects.hooks.add('afterAssociate', afterAssociate);
this.Projects.hasMany(this.Tasks, { hooks: true });
const afterAssociateArgs = afterAssociate.getCall(0).args;
......@@ -261,7 +261,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => {
});
it('should not trigger association hooks', function() {
const afterAssociate = sinon.spy();
this.Projects.afterAssociate(afterAssociate);
this.Projects.hooks.add('afterAssociate', afterAssociate);
this.Projects.hasMany(this.Tasks, { hooks: false });
expect(afterAssociate).to.not.have.been.called;
});
......
......@@ -70,7 +70,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => {
describe('beforeHasOneAssociate', () => {
it('should trigger', function() {
const beforeAssociate = sinon.spy();
this.Projects.beforeAssociate(beforeAssociate);
this.Projects.hooks.add('beforeAssociate', beforeAssociate);
this.Projects.hasOne(this.Tasks, { hooks: true });
const beforeAssociateArgs = beforeAssociate.getCall(0).args;
......@@ -88,7 +88,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => {
});
it('should not trigger association hooks', function() {
const beforeAssociate = sinon.spy();
this.Projects.beforeAssociate(beforeAssociate);
this.Projects.hooks.add('beforeAssociate', beforeAssociate);
this.Projects.hasOne(this.Tasks, { hooks: false });
expect(beforeAssociate).to.not.have.been.called;
});
......@@ -96,7 +96,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => {
describe('afterHasOneAssociate', () => {
it('should trigger', function() {
const afterAssociate = sinon.spy();
this.Projects.afterAssociate(afterAssociate);
this.Projects.hooks.add('afterAssociate', afterAssociate);
this.Projects.hasOne(this.Tasks, { hooks: true });
const afterAssociateArgs = afterAssociate.getCall(0).args;
......@@ -116,7 +116,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => {
});
it('should not trigger association hooks', function() {
const afterAssociate = sinon.spy();
this.Projects.afterAssociate(afterAssociate);
this.Projects.hooks.add('afterAssociate', afterAssociate);
this.Projects.hasOne(this.Tasks, { hooks: false });
expect(afterAssociate).to.not.have.been.called;
});
......
......@@ -37,7 +37,7 @@ describe('connection manager', () => {
const username = Math.random().toString(),
password = Math.random().toString();
this.sequelize.beforeConnect(config => {
this.sequelize.hooks.add('beforeConnect', config => {
config.username = username;
config.password = password;
return config;
......@@ -55,7 +55,7 @@ describe('connection manager', () => {
it('should call afterConnect', function() {
const spy = sinon.spy();
this.sequelize.afterConnect(spy);
this.sequelize.hooks.add('afterConnect', spy);
const connectionManager = new ConnectionManager(this.dialect, this.sequelize);
......
......@@ -89,8 +89,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => {
it('should run beforeValidate and afterValidate hooks when _validate is successful', function() {
const beforeValidate = sinon.spy();
const afterValidate = sinon.spy();
this.User.beforeValidate(beforeValidate);
this.User.afterValidate(afterValidate);
this.User.hooks.add('beforeValidate', beforeValidate);
this.User.hooks.add('afterValidate', afterValidate);
return expect(this.successfulInstanceValidator._validateAndRunHooks()).to.be.fulfilled.then(() => {
expect(beforeValidate).to.have.been.calledOnce;
......@@ -103,8 +103,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => {
sinon.stub(failingInstanceValidator, '_validate').rejects(new Error());
const beforeValidate = sinon.spy();
const afterValidate = sinon.spy();
this.User.beforeValidate(beforeValidate);
this.User.afterValidate(afterValidate);
this.User.hooks.add('beforeValidate', beforeValidate);
this.User.hooks.add('afterValidate', afterValidate);
return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(() => {
expect(beforeValidate).to.have.been.calledOnce;
......@@ -113,7 +113,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => {
});
it('should emit an error from after hook when afterValidate fails', function() {
this.User.afterValidate(() => {
this.User.hooks.add('afterValidate', () => {
throw new Error('after validation error');
});
......@@ -125,7 +125,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => {
const failingInstanceValidator = new InstanceValidator(new this.User());
sinon.stub(failingInstanceValidator, '_validate').rejects(new Error());
const validationFailedHook = sinon.spy();
this.User.validationFailed(validationFailedHook);
this.User.hooks.add('validationFailed', validationFailedHook);
return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(() => {
expect(validationFailedHook).to.have.been.calledOnce;
......@@ -136,7 +136,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => {
const failingInstanceValidator = new InstanceValidator(new this.User());
sinon.stub(failingInstanceValidator, '_validate').rejects(new SequelizeValidationError());
const validationFailedHook = sinon.stub().resolves();
this.User.validationFailed(validationFailedHook);
this.User.hooks.add('validationFailed', validationFailedHook);
return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(err => {
expect(err.name).to.equal('SequelizeValidationError');
......@@ -147,7 +147,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => {
const failingInstanceValidator = new InstanceValidator(new this.User());
sinon.stub(failingInstanceValidator, '_validate').rejects(new SequelizeValidationError());
const validationFailedHook = sinon.stub().throws(new Error('validation failed hook error'));
this.User.validationFailed(validationFailedHook);
this.User.hooks.add('validationFailed', validationFailedHook);
return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(err => {
expect(err.message).to.equal('validation failed hook error');
......
......@@ -10,11 +10,25 @@ import Model, {
ModelAttributes,
ModelOptions,
UpdateOptions,
SaveOptions,
UpsertOptions,
RestoreOptions,
} from './model';
import { Config, Options, Sequelize, SyncOptions } from './sequelize';
import { Association, AssociationOptions, Transaction } from '..';
export type HookReturn = Promise<void> | void;
export interface AssociateBeforeData<S extends Model = Model, T extends Model = Model> {
source: S;
target: T;
type: typeof Association;
}
export interface AssociateAfterData<S extends Model = Model, T extends Model = Model> extends AssociateBeforeData<S, T> {
association: Association<S, T>;
}
/**
* Options for Model.init. We mostly duplicate the Hooks here, since there is no way to combine the two
* interfaces.
......@@ -22,29 +36,51 @@ export type HookReturn = Promise<void> | void;
export interface ModelHooks<M extends Model = Model> {
beforeValidate(instance: M, options: ValidationOptions): HookReturn;
afterValidate(instance: M, options: ValidationOptions): HookReturn;
beforeCreate(attributes: M, options: CreateOptions): HookReturn;
afterCreate(attributes: M, options: CreateOptions): HookReturn;
beforeDestroy(instance: M, options: InstanceDestroyOptions): HookReturn;
afterDestroy(instance: M, options: InstanceDestroyOptions): HookReturn;
beforeUpdate(instance: M, options: InstanceUpdateOptions): HookReturn;
afterUpdate(instance: M, options: InstanceUpdateOptions): HookReturn;
beforeSave(instance: M, options: InstanceUpdateOptions | CreateOptions): HookReturn;
afterSave(instance: M, options: InstanceUpdateOptions | CreateOptions): HookReturn;
beforeBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn;
afterBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn;
beforeBulkDestroy(options: DestroyOptions): HookReturn;
afterBulkDestroy(options: DestroyOptions): HookReturn;
beforeBulkUpdate(options: UpdateOptions): HookReturn;
afterBulkUpdate(options: UpdateOptions): HookReturn;
beforeFind(options: FindOptions): HookReturn;
afterFind(instancesOrInstance: M[] | M, options: FindOptions): HookReturn;
beforeCount(options: CountOptions): HookReturn;
beforeFindAfterExpandIncludeAll(options: FindOptions): HookReturn;
beforeFindAfterOptions(options: FindOptions): HookReturn;
afterFind(instancesOrInstance: M[] | M | null, options: FindOptions): HookReturn;
beforeSync(options: SyncOptions): HookReturn;
afterSync(options: SyncOptions): HookReturn;
beforeBulkSync(options: SyncOptions): HookReturn;
afterBulkSync(options: SyncOptions): HookReturn;
beforeUpsert(values: object, options: UpsertOptions): HookReturn;
afterUpsert(instance: M, options: UpsertOptions): HookReturn;
beforeAssociate(assoc: AssociateBeforeData, options: AssociationOptions): HookReturn;
afterAssociate(assoc: AssociateAfterData, options: AssociationOptions): HookReturn;
beforeRestore(instance: M, options: RestoreOptions): HookReturn;
afterRestore(instance: M, options: RestoreOptions): HookReturn;
}
export interface SequelizeHooks extends ModelHooks {
......@@ -59,50 +95,19 @@ export interface SequelizeHooks extends ModelHooks {
/**
* Virtual class for deduplication
*/
export class Hooks {
export class Hooks<H extends object> {
/**
* Add a hook to the model
*
* @param name Provide a name for the hook function. It can be used to remove the hook later or to order
* hooks based on some sort of priority system in the future.
*/
public static addHook<C extends typeof Hooks, K extends keyof SequelizeHooks>(
hookType: K,
name: string,
fn: SequelizeHooks[K]
): C;
public static addHook<C extends typeof Hooks, K extends keyof SequelizeHooks>(
hookType: K,
fn: SequelizeHooks[K]
): C;
/**
* Remove hook from the model
*/
public static removeHook<C extends typeof Hooks, K extends keyof SequelizeHooks>(hookType: K, name: string): C;
add<K extends keyof H>(hookType: K, fn: H[K]): this;
/**
* Check whether the mode has any hooks of this type
*/
public static hasHook<K extends keyof SequelizeHooks>(hookType: K): boolean;
public static hasHooks<K extends keyof SequelizeHooks>(hookType: K): boolean;
/**
* Add a hook to the model
*
* @param name Provide a name for the hook function. It can be used to remove the hook later or to order
* hooks based on some sort of priority system in the future.
*/
public addHook<K extends keyof SequelizeHooks>(hookType: K, name: string, fn: SequelizeHooks[K]): this;
public addHook<K extends keyof SequelizeHooks>(hookType: K, fn: SequelizeHooks[K]): this;
/**
* Remove hook from the model
*/
public removeHook<K extends keyof SequelizeHooks>(hookType: K, name: string): this;
remove<K extends keyof H>(hookType: K, fn: Function): this;
/**
* Check whether the mode has any hooks of this type
*/
public hasHook<K extends keyof SequelizeHooks>(hookType: K): boolean;
public hasHooks<K extends keyof SequelizeHooks>(hookType: K): boolean;
has<K extends keyof H>(hookType: K): boolean;
}
......@@ -2,6 +2,11 @@ import { Deferrable } from './deferrable';
import { Logging } from './model';
import { Promise } from './promise';
import { Sequelize } from './sequelize';
import { Hooks } from './hooks';
export interface TransactionHooks {
afterCommit(): void;
}
/**
* The transaction object is used to identify a running transaction. It is created by calling
......@@ -10,6 +15,9 @@ import { Sequelize } from './sequelize';
* To run a query under a transaction, you should pass the transaction in the options object.
*/
export class Transaction {
public readonly hooks: Hooks<TransactionHooks>;
constructor(sequelize: Sequelize, options: TransactionOptions);
/**
......@@ -21,11 +29,6 @@ export class Transaction {
* Rollback (abort) the transaction
*/
public rollback(): Promise<void>;
/**
* Adds hook that is run after a transaction is committed
*/
public afterCommit(fn: (transaction: this) => void | Promise<void>): void;
}
// tslint:disable-next-line no-namespace
......
......@@ -3,7 +3,7 @@ import { User } from 'models/User';
export const sequelize = new Sequelize('uri');
sequelize.afterBulkSync((options: SyncOptions) => {
sequelize.hooks.add('afterBulkSync', (options: SyncOptions) => {
console.log('synced');
});
......
......@@ -5,11 +5,14 @@ import { ModelHooks } from "../lib/hooks";
* covers types/lib/sequelize.d.ts
*/
Sequelize.beforeSave((t: TestModel, options: SaveOptions) => {});
Sequelize.afterSave((t: TestModel, options: SaveOptions) => {});
Sequelize.afterFind((t: TestModel[] | TestModel | null, options: FindOptions) => {});
Sequelize.afterFind('namedAfterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => {});
Sequelize.hooks.add('beforeSave', (t: TestModel, options: SaveOptions) => {});
Sequelize.hooks.add('afterSave', (t: TestModel, options: SaveOptions) => {});
Sequelize.hooks.add('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => {});
Sequelize.hooks.add('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => {});
Sequelize.hooks.add('beforeSave', m => {
});
/*
* covers types/lib/hooks.d.ts
*/
......@@ -33,14 +36,7 @@ const hooks: Partial<ModelHooks> = {
TestModel.init({}, {sequelize, hooks })
TestModel.addHook('beforeSave', (t: TestModel, options: SaveOptions) => { });
TestModel.addHook('afterSave', (t: TestModel, options: SaveOptions) => { });
TestModel.addHook('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => { });
/*
* covers types/lib/model.d.ts
*/
TestModel.hooks.add('beforeSave', (t: TestModel, options: SaveOptions) => { });
TestModel.hooks.add('afterSave', (t: TestModel, options: SaveOptions) => { });
TestModel.hooks.add('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => { });
TestModel.beforeSave((t: TestModel, options: SaveOptions) => { });
TestModel.afterSave((t: TestModel, options: SaveOptions) => { });
TestModel.afterFind((t: TestModel | TestModel[] | null, options: FindOptions) => { });
......@@ -71,22 +71,26 @@ User.init(
}
);
User.afterSync(() => {
User.hooks.add('afterSync', () => {
sequelize.getQueryInterface().addIndex(User.tableName, {
fields: ['lastName'],
using: 'BTREE',
name: 'lastNameIdx',
concurrently: true,
fields: ['lastName'],
using: 'BTREE',
name: 'lastNameIdx',
concurrently: true,
})
})
// Hooks
User.afterFind((users, options) => {
User.hooks.add('afterFind', (users: User[], options: FindOptions) => {
console.log('found');
});
User.hooks.add('afterBulkCreate', (users: User[]) => {
})
// TODO: VSCode shows the typing being correctly narrowed but doesn't do it correctly
User.addHook('beforeFind', 'test', (options: FindOptions) => {
User.hooks.add('beforeFind', (options: FindOptions) => {
return undefined;
});
......
......@@ -18,29 +18,30 @@ const conn = sequelize.connectionManager;
// hooks
sequelize.beforeCreate('test', () => {
sequelize.hooks.add('beforeCreate', () => {
// noop
});
sequelize
.addHook('beforeConnect', (config: Config) => {
.hooks.add('beforeConnect', (config: Config) => {
// noop
})
.addHook('beforeBulkSync', () => {
.add('beforeBulkSync', () => {
// noop
});
Sequelize.addHook('beforeCreate', () => {
Sequelize.hooks.add('beforeCreate', () => {
// noop
}).addHook('beforeBulkCreate', () => {
})
.add('beforeBulkCreate', () => {
// noop
});
Sequelize.beforeConnect(() => {
Sequelize.hooks.add('beforeConnect', () => {
});
Sequelize.afterConnect(() => {
Sequelize.hooks.add('afterConnect', () => {
});
......
......@@ -5,7 +5,7 @@ export const sequelize = new Sequelize('uri');
async function trans() {
const a: number = await sequelize.transaction(async transaction => {
transaction.afterCommit(() => console.log('transaction complete'));
transaction.hooks.add('afterCommit', () => console.log('transaction complete'));
User.create(
{
data: 123,
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!