'use strict'; var Utils = require('./utils') , Promise = require('./promise'); /** * Hooks are function that are called before and after (bulk-) creation/updating/deletion and validation. Hooks can be added to you models in three ways: * * 1. By specifying them as options in `sequelize.define` * 2. By calling `hook()` with a string and your hook handler function * 3. By calling the function with the same name as the hook you want * ```js * // Method 1 * sequelize.define(name, { attributes }, { * hooks: { * beforeBulkCreate: function () { * // can be a single function * }, * beforeValidate: [ * function () {}, * function() {} // Or an array of several * ] * } * }) * * // Method 2 * Model.hook('afterDestroy', function () {}) * * // Method 3 * Model.afterBulkUpdate(function () {}) * ``` * * @see {Sequelize#define} * @mixin Hooks * @name Hooks */ var hookTypes = { beforeValidate: {params: 2}, afterValidate: {params: 2}, beforeCreate: {params: 2}, afterCreate: {params: 2}, beforeDestroy: {params: 2}, afterDestroy: {params: 2}, beforeUpdate: {params: 2}, afterUpdate: {params: 2}, beforeBulkCreate: {params: 2}, afterBulkCreate: {params: 2}, beforeBulkDestroy: {params: 1}, afterBulkDestroy: {params: 1}, beforeBulkUpdate: {params: 1}, afterBulkUpdate: {params: 1}, beforeFind: {params: 1}, beforeFindAfterExpandIncludeAll: {params: 1}, beforeFindAfterOptions: {params: 1}, afterFind: {params: 2}, beforeDefine: {params: 2, sync: true}, afterDefine: {params: 1, sync: true}, beforeInit: {params: 2, sync: true}, afterInit: {params: 1, sync: true} }; var hookAliases = { beforeDelete: 'beforeDestroy', afterDelete: 'afterDestroy', beforeBulkDelete: 'beforeBulkDestroy', afterBulkDelete: 'afterBulkDestroy' }; var Hooks = { replaceHookAliases: function(hooks) { var realHookName; Utils._.each(hooks, function(hooksArray, name) { // Does an alias for this hook name exist? if (realHookName = hookAliases[name]) { // Add the hooks to the actual hook hooks[realHookName] = (hooks[realHookName] || []).concat(hooksArray); // Delete the alias delete hooks[name]; } }); return hooks; }, runHooks: function(hooks) { var self = this , fn , fnArgs = Utils.sliceArgs(arguments, 1) , hookType; if (typeof fnArgs[fnArgs.length - 1] === 'function') { fn = fnArgs.pop(); } if (typeof hooks === 'string') { hookType = hooks; hooks = this.options.hooks[hookType] || []; if (!Array.isArray(hooks)) hooks = hooks === undefined ? [] : [hooks]; if (this.sequelize) hooks = hooks.concat(this.sequelize.options.hooks[hookType] || []); } if (!Array.isArray(hooks)) { hooks = hooks === undefined ? [] : [hooks]; } // run hooks as sync functions if flagged as sync if (hookTypes[hookType] && hookTypes[hookType].sync) { hooks.forEach(function(hook) { if (typeof hook === 'object') hook = hook.fn; return hook.apply(self, fnArgs); }); return; } // run hooks async var promise = Promise.each(hooks, function (hook) { if (typeof hook === 'object') { hook = hook.fn; } if (hookType && hook.length > hookTypes[hookType].params) { hook = Promise.promisify(hook, self); } return hook.apply(self, fnArgs); }).return(); if (fn) { return promise.nodeify(fn); } return promise; }, hook: function() { return Hooks.addHook.apply(this, arguments); }, /** * Add a hook to the model * * @param {String} hooktype * @param {String} [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. * @param {Function} fn The hook function * * @alias hook */ addHook: function(hookType, name, fn) { if (typeof name === 'function') { fn = name; name = null; } hookType = hookAliases[hookType] || hookType; this.options.hooks[hookType] = this.options.hooks[hookType] || []; this.options.hooks[hookType].push(!!name ? {name: name, fn: fn} : fn); return this; }, /** * Remove hook from the model * * @param {String} hookType * @param {String} name */ removeHook: function(hookType, name) { hookType = hookAliases[hookType] || hookType; if (!this.hasHook(hookType)) { return this; } this.options.hooks[hookType] = this.options.hooks[hookType].filter(function (hook) { // don't remove unnamed hooks if (typeof hook === 'function') { return true; } return typeof hook === 'object' && hook.name !== name; }); return this; }, /* * Check whether the mode has any hooks of this type * * @param {String} hookType * * @alias hasHooks */ hasHook: function(hookType) { return this.options.hooks[hookType] && !!this.options.hooks[hookType].length; }, }; Hooks.hasHooks = Hooks.hasHook; /** * A hook that is run before validation * @param {String} name * @param {Function} fn A callback function that is called with instance, options, callback(err) * @name beforeValidate */ /** * A hook that is run after validation * @param {String} name * @param {Function} fn A callback function that is called with instance, options, callback(err) * @name afterValidate */ /** * A hook that is run before creating a single instance * @param {String} name * @param {Function} fn A callback function that is called with attributes, options, callback(err) * @name beforeCreate */ /** * A hook that is run after creating a single instance * @param {String} name * @param {Function} fn A callback function that is called with attributes, options, callback(err) * @name afterCreate */ /** * A hook that is run before destroying a single instance * @param {String} name * @param {Function} fn A callback function that is called with instance, options, callback(err) * * @name beforeDestroy * @alias beforeDelete */ /** * A hook that is run after destroying a single instance * @param {String} name * @param {Function} fn A callback function that is called with instance, options, callback(err) * * @name afterDestroy * @alias afterDelete */ /** * A hook that is run before updating a single instance * @param {String} name * @param {Function} fn A callback function that is called with instance, options, callback(err) * @name beforeUpdate */ /** * A hook that is run after updating a single instance * @param {String} name * @param {Function} fn A callback function that is called with instance, options, callback(err) * @name afterUpdate */ /** * A hook that is run before creating instances in bulk * @param {String} name * @param {Function} fn A callback function that is called with instances, options, callback(err) * @name beforeBulkCreate */ /** * A hook that is run after creating instances in bulk * @param {String} name * @param {Function} fn A callback function that is called with instances, options, callback(err) * @name afterBulkCreate */ /** * A hook that is run before destroying instances in bulk * @param {String} name * @param {Function} fn A callback function that is called with options, callback(err) * * @name beforeBulkDestroy * @alias beforeBulkDelete */ /** * A hook that is run after destroying instances in bulk * @param {String} name * @param {Function} fn A callback function that is called with options, callback(err) * * @name afterBulkDestroy * @alias afterBulkDelete */ /** * A hook that is run after updating instances in bulk * @param {String} name * @param {Function} fn A callback function that is called with options, callback(err) * @name beforeBulkUpdate */ /** * A hook that is run after updating instances in bulk * @param {String} name * @param {Function} fn A callback function that is called with options, callback(err) * @name afterBulkUpdate */ /** * A hook that is run before a find (select) query * @param {String} name * @param {Function} fn A callback function that is called with options, callback(err) * @name beforeFind */ /** * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded * @param {String} name * @param {Function} fn A callback function that is called with options, callback(err) * @name beforeFindAfterExpandIncludeAll */ /** * A hook that is run before a find (select) query, after all option parsing is complete * @param {String} name * @param {Function} fn A callback function that is called with options, callback(err) * @name beforeFindAfterOptions */ /** * A hook that is run after a find (select) query * @param {String} name * @param {Function} fn A callback function that is called with instance(s), options, callback(err) * @name afterFind */ /** * A hook that is run before a define call * @param {String} name * @param {Function} fn A callback function that is called with attributes, options, callback(err) * @name beforeDefine */ /** * A hook that is run after a define call * @param {String} name * @param {Function} fn A callback function that is called with factory, callback(err) * @name afterDefine */ /** * A hook that is run before Sequelize() call * @param {String} name * @param {Function} fn A callback function that is called with config, options, callback(err) * @name beforeInit */ /** * A hook that is run after Sequelize() call * @param {String} name * @param {Function} fn A callback function that is called with sequelize, callback(err) * @name afterInit */ module.exports = { hooks: hookTypes, hookAliases: hookAliases, applyTo: function(Model) { Utils._.mixin(Model, Hooks); Utils._.mixin(Model.prototype, Hooks); var allHooks = Object.keys(hookTypes).concat(Object.keys(hookAliases)); allHooks.forEach(function(hook) { Model.prototype[hook] = function(callback) { return this.addHook(hook, callback); }; }); } };