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

Commit 7b333af3 by Jan Aagaard Meier

[ci skip] 📝 Update docs to mention global hooks. Closes #1028

1 parent 891ae0f0
Showing with 79 additions and 60 deletions
Hooks (also known as callbacks or lifecycle events), are functions which are called before and after calls in sequelize are executed. For example, if you want to always set a value on a model before saving it, you can add a `beforeUpdate` hook. Hooks (also known as callbacks or lifecycle events), are functions which are called before and after calls in sequelize are executed. For example, if you want to always set a value on a model before saving it, you can add a `beforeUpdate` hook.
For a full list of hooks, see [Hooks API](/api/hooks).
## Order of Operations ## Order of Operations
``` ```
...@@ -32,9 +34,9 @@ Hooks (also known as callbacks or lifecycle events), are functions which are cal ...@@ -32,9 +34,9 @@ Hooks (also known as callbacks or lifecycle events), are functions which are cal
``` ```
## Declaring Hooks ## Declaring Hooks
Arguments to hooks are passed by reference. This means, that you can change the values, and this will be reflected in the insert / update statement. A hook may contain async actions - in this case the hook function should return a promise.
There are currently three ways to programmatically add hooks. A hook function always runs asynchronousĺy, and can be resolved either by calling a callback (passed as the last argument), There are currently three ways to programmatically add hooks:
or by returning a promise.
```js ```js
// Method 1 via the .define() method // Method 1 via the .define() method
...@@ -46,29 +48,18 @@ var User = sequelize.define('User', { ...@@ -46,29 +48,18 @@ var User = sequelize.define('User', {
} }
}, { }, {
hooks: { hooks: {
beforeValidate: function(user, options, fn) { beforeValidate: function(user, options) {
user.mood = 'happy' user.mood = 'happy'
fn(null, user)
}, },
afterValidate: function(user, options, fn) { afterValidate: function(user, options) {
user.username = 'Toni' user.username = 'Toni'
fn(null, user)
} }
} }
}) })
// Method 2 via the .hook() method // Method 2 via the .hook() method
var User = sequelize.define('User', { User.hook('beforeValidate', function(user, options) {
username: DataTypes.STRING,
mood: {
type: DataTypes.ENUM,
values: ['happy', 'sad', 'neutral']
}
})
User.hook('beforeValidate', function(user, options, fn) {
user.mood = 'happy' user.mood = 'happy'
fn(null, user)
}) })
User.hook('afterValidate', function(user, options) { User.hook('afterValidate', function(user, options) {
...@@ -76,22 +67,14 @@ User.hook('afterValidate', function(user, options) { ...@@ -76,22 +67,14 @@ User.hook('afterValidate', function(user, options) {
}) })
// Method 3 via the direct method // Method 3 via the direct method
var User = sequelize.define('User', { User.beforeCreate(function(user, options) {
username: DataTypes.STRING, return hashPassword(user.password).then(function (hashedPw) {
mood: { user.password = hashedPw;
type: DataTypes.ENUM, });
values: ['happy', 'sad', 'neutral']
}
})
User.beforeValidate(function(user, options) {
user.mood = 'happy'
return sequelize.Promise.resolve(user)
}) })
User.afterValidate(function(user, options, fn) { User.afterValidate(function(user, options) {
user.username = 'Toni' user.username = 'Toni'
fn(null, user)
}) })
``` ```
...@@ -111,9 +94,68 @@ Book.addHook('afterCreate', 'notifyUsers', function(book, options) { ...@@ -111,9 +94,68 @@ Book.addHook('afterCreate', 'notifyUsers', function(book, options) {
Book.removeHook('afterCreate', 'notifyUsers') Book.removeHook('afterCreate', 'notifyUsers')
``` ```
## Global / universal hooks
Global hooks are hooks which are run for all models. They can define behaviours that you want for all your models, and are especially useful for plugins. They can be defined in two ways, which have slightly different semantics:
### Sequelize.options.define (default hook)
```js
var sequelize = new Sequelize(..., {
define: {
hooks: {
beforeCreate: function () {
// Do stuff
}
}
}
});
```
This adds a default hook to all models, which is run if the model does not define its own `beforeCreate` hook:
```js
var User = sequelize.define('user');
var Project = sequelize.define('project', {}, {
hooks: {
beforeCreate: function () {
// Do other stuff
}
}
});
User.create() // Runs the global hook
Project.create() // Runs its own hook (because the global hook is overwritten)
```
### Sequelize.addHook (permanent hook)
```js
sequelize.addHook('beforeCreate', function () {
// Do stuff
});
```
This hooks is always run before create, regardless of whether the model specifies its own `beforeCreate` hook:
```js
var User = sequelize.define('user');
var Project = sequelize.define('project', {}, {
hooks: {
beforeCreate: function () {
// Do other stuff
}
}
});
User.create() // Runs the global hook
Project.create() // Runs its own hook, followed by the global hook
```
Local hooks are always run before global hooks.
### Instance hooks ### Instance hooks
The following hooks will emit whenever you're editing a single object... The following hooks will emit whenever you're editing a single object
``` ```
beforeValidate beforeValidate
...@@ -169,7 +211,7 @@ Model.update({username: 'Toni'}, { where: {accessLevel: 0}, individualHooks: tru ...@@ -169,7 +211,7 @@ Model.update({username: 'Toni'}, { where: {accessLevel: 0}, individualHooks: tru
Some model hooks have two or three parameters sent to each hook depending on it's type. Some model hooks have two or three parameters sent to each hook depending on it's type.
```js ```js
Model.beforeBulkCreate(function(records, fields, fn) { Model.beforeBulkCreate(function(records, fields) {
// records = the first argument sent to .bulkCreate // records = the first argument sent to .bulkCreate
// fields = the second argument sent to .bulkCreate // fields = the second argument sent to .bulkCreate
}) })
...@@ -179,14 +221,14 @@ Model.bulkCreate([ ...@@ -179,14 +221,14 @@ Model.bulkCreate([
{username: 'Tobi'} // part of records argument {username: 'Tobi'} // part of records argument
], ['username'] /* part of fields argument */) ], ['username'] /* part of fields argument */)
Model.beforeBulkUpdate(function(attributes, where, fn) { Model.beforeBulkUpdate(function(attributes, where) {
// attributes = first argument sent to Model.update // attributes = first argument sent to Model.update
// where = second argument sent to Model.update // where = second argument sent to Model.update
}) })
Model.update({gender: 'Male'} /*attributes argument*/, { where: {username: 'Tom'}} /*where argument*/) Model.update({gender: 'Male'} /*attributes argument*/, { where: {username: 'Tom'}} /*where argument*/)
Model.beforeBulkDestroy(function(whereClause, fn) { Model.beforeBulkDestroy(function(whereClause) {
// whereClause = first argument sent to Model.destroy // whereClause = first argument sent to Model.destroy
}) })
...@@ -197,8 +239,9 @@ Model.destroy({ where: {username: 'Tom'}} /*whereClause argument*/) ...@@ -197,8 +239,9 @@ Model.destroy({ where: {username: 'Tom'}} /*whereClause argument*/)
For the most part hooks will work the same for instances when being associated except a few things For the most part hooks will work the same for instances when being associated except a few things
1. When using add/set\[s\] functions the beforeUpdate/afterUpdate hooks will run. 1. When using add/set functions the beforeUpdate/afterUpdate hooks will run.
2. The only way to call beforeDestroy/afterDestroy hooks are on associations with `onDelete: 'cascade'` and the option `hooks: true`. For instance: 2. The only way to call beforeDestroy/afterDestroy hooks are on associations with `onDelete: 'cascade'` and the option `hooks: true`. For instance:
```js ```js
var Projects = sequelize.define('Projects', { var Projects = sequelize.define('Projects', {
title: DataTypes.STRING title: DataTypes.STRING
...@@ -208,12 +251,11 @@ var Tasks = sequelize.define('Tasks', { ...@@ -208,12 +251,11 @@ var Tasks = sequelize.define('Tasks', {
title: DataTypes.STRING title: DataTypes.STRING
}) })
Projects.hasMany(Tasks, {onDelete: 'cascade', hooks: true}) Projects.hasMany(Tasks, { onDelete: 'cascade', hooks: true })
Tasks.belongsTo(Projects) Tasks.belongsTo(Projects)
``` ```
This code will run beforeDestroy/afterDestroy on the Tasks table. Sequelize, by default, will try to optimize your queries as much as possible. This code will run beforeDestroy/afterDestroy on the Tasks table. Sequelize, by default, will try to optimize your queries as much as possible. When calling cascade on delete, Sequelize will simply execute a
When calling cascade on delete, Sequelize will simply execute a
```sql ```sql
DELETE FROM `table` WHERE associatedIdentifiier = associatedIdentifier.primaryKey DELETE FROM `table` WHERE associatedIdentifiier = associatedIdentifier.primaryKey
...@@ -221,29 +263,6 @@ DELETE FROM `table` WHERE associatedIdentifiier = associatedIdentifier.primaryKe ...@@ -221,29 +263,6 @@ DELETE FROM `table` WHERE associatedIdentifiier = associatedIdentifier.primaryKe
However, adding `hooks: true` explicitly tells Sequelize that optimization is not of your concern and will perform a `SELECT` on the associated objects and destroy each instance one by one in order to be able to call the hooks with the right parameters. However, adding `hooks: true` explicitly tells Sequelize that optimization is not of your concern and will perform a `SELECT` on the associated objects and destroy each instance one by one in order to be able to call the hooks with the right parameters.
## Promises and callbacks
Sequelize will look at the function length of your hook callback to determine whether or not you're using callbacks or promises.
```js
// Will stall if the condition isn't met since the callback is never called
User.beforeCreate(function(user, options, callback) {
if (user.accessLevel > 10 && user.username !== "Boss") {
return callback("You can't grant this user an access level above 10!");
}
});
// Will never stall since returning undefined will act as a resolved promise with an undefined value
User.beforeCreate(function(user, options) {
if (user.accessLevel > 10 && user.username !== "Boss") {
return throw new Error("You can't grant this user an access level above 10!");
}
if (something) {
return Promise.reject();
}
});
```
## A Note About Transactions ## A Note About Transactions
Note that many model operations in Sequelize allow you to specify a transaction in the options parameter of the method. If a transaction _is_ specified in the original call, it will be present in the options parameter passed to the hook function. For example, consider the following snippet: Note that many model operations in Sequelize allow you to specify a transaction in the options parameter of the method. If a transaction _is_ specified in the original call, it will be present in the options parameter passed to the hook function. For example, consider the following snippet:
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!