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

Commit 03f49f65 by Mick Hansen

Merge pull request #2240 from overlookmotel/hooks-options

Options passed to hooks
2 parents 87f1c86f b5f31a88
......@@ -5,10 +5,15 @@
- [FEATURE] Added `scope` to hasMany association definitions, provides default values to association setters/finders [#2268](https://github.com/sequelize/sequelize/pull/2268)
- [FEATURE] We now support transactions that automatically commit/rollback based on the result of the promise chain returned to the callback.
- [BUG] Only try to create indexes which don't already exist. Closes [#2162](https://github.com/sequelize/sequelize/issues/2162)
- [FEATURE] Hooks are passed options
- [FEATURE] Hooks need not return a result - undefined return is interpreted as a resolved promise
- [FEATURE] Added `find()` hooks
#### Backwards compatability changes
- The `fieldName` property, used in associations with a foreign key object `(A.hasMany(B, { foreignKey: { ... }})`, has been renamed to `name` to avoid confusion with `field`.
- The naming of the join table entry for N:M association getters is now singular (like includes)
- Signature of hooks has changed to pass options to all hooks
- Results returned by hooks are ignored - changes to results by hooks should be made by reference
# v2.0.0-dev13
We are working our way to the first 2.0.0 release candidate.
......@@ -18,9 +23,9 @@ We are working our way to the first 2.0.0 release candidate.
- [FEATURE] Added support for passing an `indexes` array in options to `sequelize.define`. [#1485](https://github.com/sequelize/sequelize/issues/1485). See API reference for details.
- [FEATURE/INTERNALS] Standardized the output from `QueryInterface.showIndex`.
- [FEATURE] Include deleted rows in find [#2083](https://github.com/sequelize/sequelize/pull/2083)
- [FEATURE] Make addSingular and addPlural for n:m assocations (fx `addUser` and `addUsers` now both accept an array or an instance.
- [FEATURE] Make addSingular and addPlural for n:m associations (fx `addUser` and `addUsers` now both accept an array or an instance.
- [BUG] Hid `dottie.transform` on raw queries behind a flag (`nest`) [#2064](https://github.com/sequelize/sequelize/pull/2064)
- [BUG] Fixed problems with transcation parameter being removed / not passed on in associations [#1789](https://github.com/sequelize/sequelize/issues/1789) and [#1968](https://github.com/sequelize/sequelize/issues/1968)
- [BUG] Fixed problems with transaction parameter being removed / not passed on in associations [#1789](https://github.com/sequelize/sequelize/issues/1789) and [#1968](https://github.com/sequelize/sequelize/issues/1968)
- [BUG] Fix problem with minConnections. [#2048](https://github.com/sequelize/sequelize/issues/2048)
- [BUG] Fix default scope being overwritten [#2087](https://github.com/sequelize/sequelize/issues/2087)
- [BUG] Fixed updatedAt timestamp not being set in bulk create when validate = true. [#1962](https://github.com/sequelize/sequelize/issues/1962)
......
......@@ -35,9 +35,31 @@ var Utils = require('./utils')
* @mixin Hooks
*/
var Hooks = module.exports = function() {};
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}
};
var hookAliases = {
beforeDelete: 'beforeDestroy',
afterDelete: 'afterDestroy'
afterDelete: 'afterDestroy',
beforeBulkDelete: 'beforeBulkDestroy',
afterBulkDelete: 'afterBulkDestroy'
};
Hooks.replaceHookAliases = function(hooks) {
......@@ -57,16 +79,18 @@ Hooks.replaceHookAliases = function(hooks) {
return hooks;
};
Hooks.runHooks = function() {
Hooks.runHooks = function(hooks) {
var self = this
, tick = 0
, hooks = arguments[0]
, lastIndex = arguments.length - 1
, fn = typeof arguments[lastIndex] === 'function' ? arguments[lastIndex] : null
, fnArgs = Array.prototype.slice.call(arguments, 1, fn ? lastIndex : arguments.length)
, resolveArgs = fnArgs;
, fn
, fnArgs = Array.prototype.slice.call(arguments, 1)
, hookType;
if (typeof fnArgs[fnArgs.length - 1] === 'function') {
fn = fnArgs.pop();
}
if (typeof hooks === 'string') {
hookType = hooks;
hooks = this.options.hooks[hooks] || [];
}
......@@ -74,53 +98,20 @@ Hooks.runHooks = function() {
hooks = hooks === undefined ? [] : [hooks];
}
var promise = new Promise(function(resolve, reject) {
if (hooks.length < 1) {
return resolve(fnArgs);
var promise = Promise.map(hooks, function(hook) {
if (typeof hook === 'object') {
hook = hook.fn;
}
var run = function(hook) {
if (!hook) {
return resolve(resolveArgs);
}
if (typeof hook === 'object') {
hook = hook.fn;
}
var maybePromise = hook.apply(self, fnArgs.concat(function() {
tick++;
if (!!arguments[0]) {
return reject(arguments[0]);
}
if (arguments.length) {
resolveArgs = Array.prototype.slice.call(arguments, 1);
}
return run(hooks[tick]);
}));
if (Utils.Promise.is(maybePromise)) {
maybePromise.spread(function() {
tick++;
if (arguments.length) {
resolveArgs = Array.prototype.slice.call(arguments);
}
return run(hooks[tick]);
}, reject);
}
};
if (hookType && hook.length > hookTypes[hookType].params) {
hook = Promise.promisify(hook, self);
}
run(hooks[tick]);
});
return hook.apply(self, fnArgs);
}, {concurrency: 1}).return();
if (fn) {
promise = promise.spread(function() {
fn.apply(self, [null].concat(Array.prototype.slice.apply(arguments)));
}, fn);
return promise.nodeify(fn);
}
return promise;
......@@ -145,23 +136,19 @@ Hooks.addHook = function(hookType, name, fn) {
name = null;
}
var method = function() {
return fn.apply(this, Array.prototype.slice.call(arguments, 0, arguments.length - 1).concat(arguments[arguments.length - 1]));
};
// Aliases
hookType = hookAliases[hookType] || hookType;
// Just in case if we override the default DAOFactory.options
this.options.hooks[hookType] = this.options.hooks[hookType] || [];
this.options.hooks[hookType][this.options.hooks[hookType].length] = !!name ? {name: name, fn: method} : method;
this.options.hooks[hookType][this.options.hooks[hookType].length] = !!name ? {name: name, fn: fn} : fn;
return this;
};
/**
* A hook that is run before validation
* @param {String} name
* @param {Function} fn A callback function that is called with instance, callback(err)
* @param {Function} fn A callback function that is called with instance, options, callback(err)
*/
Hooks.beforeValidate = function(name, fn) {
return Hooks.addHook.call(this, 'beforeValidate', name, fn);
......@@ -170,7 +157,7 @@ Hooks.beforeValidate = function(name, fn) {
/**
* A hook that is run after validation
* @param {String} name
* @param {Function} fn A callback function that is called with instance, callback(err)
* @param {Function} fn A callback function that is called with instance, options, callback(err)
*/
Hooks.afterValidate = function(name, fn) {
return Hooks.addHook.call(this, 'afterValidate', name, fn);
......@@ -179,7 +166,7 @@ Hooks.afterValidate = function(name, fn) {
/**
* A hook that is run before creating a single instance
* @param {String} name
* @param {Function} fn A callback function that is called with attributes, callback(err)
* @param {Function} fn A callback function that is called with attributes, options, callback(err)
*/
Hooks.beforeCreate = function(name, fn) {
return Hooks.addHook.call(this, 'beforeCreate', name, fn);
......@@ -188,7 +175,7 @@ Hooks.beforeCreate = function(name, fn) {
/**
* A hook that is run after creating a single instance
* @param {String} name
* @param {Function} fn A callback function that is called with attributes, callback(err)
* @param {Function} fn A callback function that is called with attributes, options, callback(err)
*/
Hooks.afterCreate = function(name, fn) {
return Hooks.addHook.call(this, 'afterCreate', name, fn);
......@@ -197,7 +184,7 @@ Hooks.afterCreate = function(name, fn) {
/**
* A hook that is run before destroying a single instance
* @param {String} name
* @param {Function} fn A callback function that is called with instance, callback(err)
* @param {Function} fn A callback function that is called with instance, options, callback(err)
*
* @alias beforeDelete
*/
......@@ -205,10 +192,14 @@ Hooks.beforeDestroy = function(name, fn) {
return Hooks.addHook.call(this, 'beforeDestroy', name, fn);
};
Hooks.beforeDelete = function(name, fn) {
return Hooks.addHook.call(this, 'beforeDelete', name, fn);
};
/**
* A hook that is run after destroying a single instance
* @param {String} name
* @param {Function} fn A callback function that is called with instance, callback(err)
* @param {Function} fn A callback function that is called with instance, options, callback(err)
*
* @alias afterDelete
*/
......@@ -216,10 +207,6 @@ Hooks.afterDestroy = function(name, fn) {
return Hooks.addHook.call(this, 'afterDestroy', name, fn);
};
Hooks.beforeDelete = function(name, fn) {
return Hooks.addHook.call(this, 'beforeDelete', name, fn);
};
Hooks.afterDelete = function(name, fn) {
return Hooks.addHook.call(this, 'afterDelete', name, fn);
};
......@@ -227,7 +214,7 @@ Hooks.afterDelete = function(name, fn) {
/**
* A hook that is run before updating a single instance
* @param {String} name
* @param {Function} fn A callback function that is called with instance, callback(err)
* @param {Function} fn A callback function that is called with instance, options, callback(err)
*/
Hooks.beforeUpdate = function(name, fn) {
return Hooks.addHook.call(this, 'beforeUpdate', name, fn);
......@@ -236,7 +223,7 @@ Hooks.beforeUpdate = function(name, fn) {
/**
* A hook that is run after updating a single instance
* @param {String} name
* @param {Function} fn A callback function that is called with instance, callback(err)
* @param {Function} fn A callback function that is called with instance, options, callback(err)
*/
Hooks.afterUpdate = function(name, fn) {
return Hooks.addHook.call(this, 'afterUpdate', name, fn);
......@@ -245,7 +232,7 @@ Hooks.afterUpdate = function(name, fn) {
/**
* A hook that is run before creating instances in bulk
* @param {String} name
* @param {Function} fn A callback function that is called with instances, fields, callback(err)
* @param {Function} fn A callback function that is called with instances, options, callback(err)
*/
Hooks.beforeBulkCreate = function(name, fn) {
return Hooks.addHook.call(this, 'beforeBulkCreate', name, fn);
......@@ -254,34 +241,46 @@ Hooks.beforeBulkCreate = function(name, fn) {
/**
* A hook that is run after creating instances in bulk
* @param {String} name
* @param {Function} fn A callback function that is called with instances, fields, callback(err)
* @param {Function} fn A callback function that is called with instances, options, callback(err)
*/
Hooks.afterBulkCreate = function(name, fn) {
return Hooks.addHook.call(this, 'afterBulkCreate', name, fn);
};
/**
* A hook that is run before destroing instances in bulk
* A hook that is run before destroying instances in bulk
* @param {String} name
* @param {Function} fn A callback function that is called with where, callback(err)
* @param {Function} fn A callback function that is called with options, callback(err)
*
* @alias beforeBulkDelete
*/
Hooks.beforeBulkDestroy = function(name, fn) {
return Hooks.addHook.call(this, 'beforeBulkDestroy', name, fn);
};
Hooks.beforeBulkDelete = function(name, fn) {
return Hooks.addHook.call(this, 'beforeBulkDelete', name, fn);
};
/**
* A hook that is run after destroying instances in bulk
* @param {String} name
* @param {Function} fn A callback function that is called with where, callback(err)
* @param {Function} fn A callback function that is called with options, callback(err)
*
* @alias afterBulkDelete
*/
Hooks.afterBulkDestroy = function(name, fn) {
return Hooks.addHook.call(this, 'afterBulkDestroy', name, fn);
};
Hooks.afterBulkDelete = function(name, fn) {
return Hooks.addHook.call(this, 'afterBulkDelete', name, fn);
};
/**
* A hook that is run after updating instances in bulk
* @param {String} name
* @param {Function} fn A callback function that is called with attribute, where, callback(err)
* @param {Function} fn A callback function that is called with options, callback(err)
*/
Hooks.beforeBulkUpdate = function(name, fn) {
return Hooks.addHook.call(this, 'beforeBulkUpdate', name, fn);
......@@ -290,8 +289,44 @@ Hooks.beforeBulkUpdate = function(name, fn) {
/**
* A hook that is run after updating instances in bulk
* @param {String} name
* @param {Function} fn A callback function that is called with attribute, where, callback(err)
* @param {Function} fn A callback function that is called with options, callback(err)
*/
Hooks.afterBulkUpdate = function(name, fn) {
return Hooks.addHook.call(this, 'afterBulkUpdate', name, fn);
};
/**
* 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)
*/
Hooks.beforeFind = function(name, fn) {
return Hooks.addHook.call(this, 'beforeFind', name, fn);
};
/**
* 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)
*/
Hooks.beforeFindAfterExpandIncludeAll = function(name, fn) {
return Hooks.addHook.call(this, 'beforeFindAfterExpandIncludeAll', name, fn);
};
/**
* 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)
*/
Hooks.beforeFindAfterOptions = function(name, fn) {
return Hooks.addHook.call(this, 'beforeFindAfterOptions', name, fn);
};
/**
* 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)
*/
Hooks.afterFind = function(name, fn) {
return Hooks.addHook.call(this, 'afterFind', name, fn);
};
......@@ -165,14 +165,14 @@ InstanceValidator.prototype.validate = function() {
*/
InstanceValidator.prototype.hookValidate = function() {
var self = this;
return self.modelInstance.Model.runHooks('beforeValidate', self.modelInstance).then(function() {
return self.modelInstance.Model.runHooks('beforeValidate', self.modelInstance, self.options).then(function() {
return self.validate().then(function(error) {
if (error) {
throw error;
}
});
}).then(function() {
return self.modelInstance.Model.runHooks('afterValidate', self.modelInstance);
return self.modelInstance.Model.runHooks('afterValidate', self.modelInstance, self.options);
}).return(self.modelInstance);
};
......
......@@ -495,7 +495,10 @@ module.exports = (function() {
return Promise.try(function() {
// Validate
if (options.hooks) {
return self.hookValidate({skip: _.difference(Object.keys(self.rawAttributes), options.fields)});
options.skip = _.difference(Object.keys(self.rawAttributes), options.fields);
return self.hookValidate(options).then(function() {
delete options.skip;
});
}
}).then(function() {
options.fields.forEach(function(field) {
......@@ -584,7 +587,7 @@ module.exports = (function() {
return Promise.try(function() {
// Run before hook
if (options.hooks) {
return self.Model.runHooks('before' + hook, self).then(function() {
return self.Model.runHooks('before' + hook, self, options).then(function() {
// dataValues might have changed inside the hook, rebuild the values hash
values = {};
......@@ -634,7 +637,7 @@ module.exports = (function() {
}).tap(function(result) {
// Run after hook
if (options.hooks) {
return self.Model.runHooks('after' + hook, result);
return self.Model.runHooks('after' + hook, result, options);
}
}).then(function(result) {
return result;
......@@ -683,8 +686,8 @@ module.exports = (function() {
return new InstanceValidator(this, options).validate();
};
Instance.prototype.hookValidate = function(object) {
var validator = new InstanceValidator(this, object);
Instance.prototype.hookValidate = function(options) {
var validator = new InstanceValidator(this, options);
return validator.hookValidate();
};
......@@ -732,7 +735,7 @@ module.exports = (function() {
return Promise.try(function() {
// Run before hook
if (options.hooks) {
return self.Model.runHooks('beforeDestroy', self);
return self.Model.runHooks('beforeDestroy', self, options);
}
}).then(function() {
var identifier;
......@@ -747,7 +750,7 @@ module.exports = (function() {
}).tap(function(result) {
// Run after hook
if (options.hooks) {
return self.Model.runHooks('afterDestroy', self);
return self.Model.runHooks('afterDestroy', self, options);
}
}).then(function(result) {
return result;
......
......@@ -693,36 +693,62 @@ module.exports = (function() {
tableNames[this.getTableName()] = true;
options = optClone(options || {});
if (typeof options === 'object') {
if (options.hasOwnProperty('include') && options.include) {
hasJoin = true;
options = Utils._.defaults(options, {
hooks: true
});
return Promise.bind(this).then(function() {
conformOptions(options);
if (options.hooks) {
return this.runHooks('beforeFind', options);
}
}).then(function() {
expandIncludeAll.call(this, options);
validateIncludedElements.call(this, options, tableNames);
if (options.hooks) {
return this.runHooks('beforeFindAfterExpandIncludeAll', options);
}
}).then(function() {
if (typeof options === 'object') {
if (options.include) {
hasJoin = true;
if (options.attributes) {
if (options.attributes.indexOf(this.primaryKeyAttribute) === -1) {
options.originalAttributes = options.attributes;
options.attributes = [this.primaryKeyAttribute].concat(options.attributes);
validateIncludedElements.call(this, options, tableNames);
if (options.attributes) {
if (options.attributes.indexOf(this.primaryKeyAttribute) === -1) {
options.originalAttributes = options.attributes;
options.attributes = [this.primaryKeyAttribute].concat(options.attributes);
}
}
}
// whereCollection is used for non-primary key updates
this.options.whereCollection = options.where || null;
}
// whereCollection is used for non-primary key updates
this.options.whereCollection = options.where || null;
}
if (options.attributes === undefined) {
options.attributes = Object.keys(this.tableAttributes);
}
mapFieldNames.call(this, options, this);
if (options.attributes === undefined) {
options.attributes = Object.keys(this.tableAttributes);
}
mapFieldNames.call(this, options, this);
options = paranoidClause.call(this, options);
options = paranoidClause.call(this, options);
return this.QueryInterface.select(this, this.getTableName(), options, Utils._.defaults({
type: QueryTypes.SELECT,
hasJoin: hasJoin,
tableNames: Object.keys(tableNames)
}, queryOptions, { transaction: (options || {}).transaction }));
if (options.hooks) {
return this.runHooks('beforeFindAfterOptions', options);
}
}).then(function() {
return this.QueryInterface.select(this, this.getTableName(), options, Utils._.defaults({
type: QueryTypes.SELECT,
hasJoin: hasJoin,
tableNames: Object.keys(tableNames)
}, queryOptions, { transaction: (options || {}).transaction }));
}).tap(function(results) {
if (options.hooks) {
return this.runHooks('afterFind', results, options);
}
});
};
//right now, the caller (has-many-double-linked) is in charge of the where clause
......@@ -818,10 +844,12 @@ module.exports = (function() {
*/
Model.prototype.count = function(options) {
options = Utils._.clone(options || {});
conformOptions(options);
var col = '*';
if (options.include) {
col = this.name + '.' + this.primaryKeyAttribute;
expandIncludeAll.call(this, options);
validateIncludedElements.call(this, options);
}
......@@ -935,8 +963,12 @@ module.exports = (function() {
});
}
if (options.hasOwnProperty('include') && options.include && !options.includeValidated) {
validateIncludedElements.call(this, options);
if (!options.includeValidated) {
conformOptions(options);
if (options.include) {
expandIncludeAll.call(this, options);
validateIncludedElements.call(this, options);
}
}
return new this.Instance(values, options);
......@@ -946,8 +978,12 @@ module.exports = (function() {
Model.prototype.bulkBuild = function(valueSets, options) {
options = options || { isNewRecord: true, isDirty: true };
if (options.hasOwnProperty('include') && options.include && !options.includeValidated) {
validateIncludedElements.call(this, options);
if (!options.includeValidated) {
conformOptions(options);
if (options.include) {
expandIncludeAll.call(this, options);
validateIncludedElements.call(this, options);
}
}
if (options.attributes) {
......@@ -1033,7 +1069,9 @@ module.exports = (function() {
var build = self.build(params);
return build.hookValidate({skip: Object.keys(params)}).then(function() {
options.skip = Object.keys(params);
return build.hookValidate(options).then(function() {
delete options.skip;
return Promise.resolve([build, true]);
});
}
......@@ -1170,10 +1208,7 @@ module.exports = (function() {
return Promise.try(function() {
// Run before hook
if (options.hooks) {
return self.runHooks('beforeBulkCreate', daos, options.fields).spread(function(_daos, _fields) {
daos = _daos || daos;
options.fields = _fields || options.fields;
});
return self.runHooks('beforeBulkCreate', daos, options);
}
}).then(function() {
daos.forEach(function(dao) {
......@@ -1196,17 +1231,18 @@ module.exports = (function() {
// Validate
if (options.validate) {
var skippedFields = Utils._.difference(Object.keys(self.attributes), options.fields);
options.skip = Utils._.difference(Object.keys(self.attributes), options.fields);
var errors = [];
return Promise.map(daos, function(dao) {
var fn = options.individualHooks ? 'hookValidate' : 'validate';
return dao[fn]({skip: skippedFields}).then(function(err) {
return dao[fn](options).then(function(err) {
if (!!err) {
errors.push({record: dao, errors: err});
}
});
}).then(function() {
delete options.skip;
if (errors.length) {
return Promise.reject(errors);
}
......@@ -1216,7 +1252,14 @@ module.exports = (function() {
if (options.individualHooks) {
// Create each dao individually
return Promise.map(daos, function(dao) {
return dao.save({transaction: options.transaction});
var individualOptions = Utils._.clone(options);
delete individualOptions.fields;
delete individualOptions.individualHooks;
delete individualOptions.ignoreDuplicates;
individualOptions.validate = false;
individualOptions.hooks = true;
return dao.save(individualOptions);
}).then(function(_daos) {
daos = _daos;
});
......@@ -1254,9 +1297,7 @@ module.exports = (function() {
}).then(function() {
// Run after hook
if (options.hooks) {
return self.runHooks('afterBulkCreate', daos, options.fields).spread(function(_daos) {
if (_daos) daos = _daos;
});
return self.runHooks('afterBulkCreate', daos, options);
}
}).then(function() {
return daos;
......@@ -1293,16 +1334,18 @@ module.exports = (function() {
return Promise.try(function() {
// Run before hook
if (options.hooks) {
return self.runHooks('beforeBulkDestroy', where).spread(function(_where) {
where = _where || where;
options.where = where;
return self.runHooks('beforeBulkDestroy', options).then(function() {
where = options.where;
delete options.where;
});
}
}).then(function() {
// Get daos and run beforeDestroy hook on each record individually
if (options.individualHooks) {
return self.all({where: where}, {transaction: options.transaction}).map(function(dao) {
return self.runHooks('beforeDestroy', dao).spread(function(_dao) {
return _dao || dao;
return self.findAll({where: where}, {transaction: options.transaction}).map(function(dao) {
return self.runHooks('beforeDestroy', dao, options).then(function() {
return dao;
});
}).then(function(_daos) {
daos = _daos;
......@@ -1321,13 +1364,16 @@ module.exports = (function() {
// Run afterDestroy hook on each record individually
if (options.individualHooks) {
return Promise.map(daos, function(dao) {
return self.runHooks('afterDestroy', dao);
return self.runHooks('afterDestroy', dao, options);
});
}
}).tap(function() {
// Run after hook
if (options.hooks) {
return self.runHooks('afterBulkDestroy', where);
options.where = where;
return self.runHooks('afterBulkDestroy', options).then(function() {
delete options.where;
});
}
}).then(function(affectedRows) {
return affectedRows;
......@@ -1377,8 +1423,9 @@ module.exports = (function() {
build.set(self._timestampAttributes.updatedAt, attrValueHash[self._timestampAttributes.updatedAt], { raw: true });
// We want to skip validations for all other fields
var skippedFields = Utils._.difference(Object.keys(self.attributes), Object.keys(attrValueHash));
return build.hookValidate({skip: skippedFields}).then(function(attributes) {
options.skip = Utils._.difference(Object.keys(self.attributes), Object.keys(attrValueHash));
return build.hookValidate(options).then(function(attributes) {
delete options.skip;
if (attributes && attributes.dataValues) {
attrValueHash = Utils._.pick(attributes.dataValues, Object.keys(attrValueHash));
}
......@@ -1387,9 +1434,13 @@ module.exports = (function() {
}).then(function() {
// Run before hook
if (options.hooks) {
return self.runHooks('beforeBulkUpdate', attrValueHash, where).spread(function(_attrValueHash, _where) {
where = _where || where;
attrValueHash = _attrValueHash || attrValueHash;
options.where = where;
options.attributes = attrValueHash;
return self.runHooks('beforeBulkUpdate', options).then(function() {
where = options.where;
attrValueHash = options.attributes;
delete options.where;
delete options.attributes;
});
}
}).then(function() {
......@@ -1397,7 +1448,7 @@ module.exports = (function() {
// Get daos and run beforeUpdate hook on each record individually
if (options.individualHooks) {
return self.all({where: where}, {transaction: options.transaction}).then(function(_daos) {
return self.findAll({where: where}, {transaction: options.transaction}).then(function(_daos) {
daos = _daos;
if (!daos.length) {
return [];
......@@ -1413,9 +1464,7 @@ module.exports = (function() {
Utils._.extend(dao.dataValues, attrValueHash);
// Run beforeUpdate hook
return self.runHooks('beforeUpdate', dao).spread(function(_dao) {
dao = _dao || dao;
return self.runHooks('beforeUpdate', dao, options).then(function() {
if (!different) {
var thisChangedValues = {};
Utils._.forIn(dao.dataValues, function(newValue, attr) {
......@@ -1447,7 +1496,12 @@ module.exports = (function() {
// Hooks change values in a different way for each record
// Do not run original query but save each record individually
return Promise.map(daos, function(dao) {
return dao.save({transaction: options.transaction, hooks: false});
var individualOptions = Utils._.clone(options);
delete individualOptions.individualHooks;
individualOptions.hooks = false;
individualOptions.validate = false;
return dao.save(individualOptions);
}).tap(function(_daos) {
daos = _daos;
});
......@@ -1464,6 +1518,7 @@ module.exports = (function() {
// Run query to update all rows
return self.QueryInterface.bulkUpdate(self.getTableName(), attrValueHashUse, where, options, self.tableAttributes).then(function(affectedRows) {
if (options.returning) {
daos = affectedRows;
return [affectedRows.length, affectedRows];
}
......@@ -1472,17 +1527,20 @@ module.exports = (function() {
}).tap(function(result) {
if (options.individualHooks) {
return Promise.map(daos, function(dao) {
return self.runHooks('afterUpdate', dao).spread(function(_dao) {
return _dao || dao;
});
}).then(function(_daos) {
result[1] = daos = _daos;
return self.runHooks('afterUpdate', dao, options);
}).then(function() {
result[1] = daos;
});
}
}).tap(function() {
// Run after hook
if (options.hooks) {
return self.runHooks('afterBulkUpdate', attrValueHash, where);
options.where = where;
options.attributes = attrValueHash;
return self.runHooks('afterBulkUpdate', options).then(function() {
delete options.where;
delete options.attributes;
});
}
}).then(function(result) {
// Return result in form [affectedRows, daos] (daos missed off if options.individualHooks != true)
......@@ -1681,46 +1739,52 @@ module.exports = (function() {
}.bind(this));
};
var validateIncludedElements = function(options, tableNames) {
tableNames = tableNames || {};
options.includeNames = [];
options.includeMap = {};
options.hasSingleAssociation = false;
options.hasMultiAssociation = false;
var conformOptions = function(options) {
if (!options.include) {
return;
}
// if include is not an array, wrap in an array
if (!Array.isArray(options.include)) {
options.include = [options.include];
} else if (!options.include.length) {
delete options.include;
return;
}
// convert all included elements to { Model: Model } form
var includes = options.include = options.include.map(function(include) {
// convert all included elements to { model: Model } form
options.include = options.include.map(function(include) {
if (include instanceof Association) {
include = { association: include };
} else if (include instanceof Model) {
include = { model: include };
} else if (typeof include !== 'object') {
throw new Error('Include unexpected. Element has to be either a Model, an Association or an object.');
} else if (include.hasOwnProperty('daoFactory')) {
include.model = include.daoFactory;
} else {
// convert daoFactory to model (for backwards compatibility)
if (include.hasOwnProperty('daoFactory')) {
include.model = include.daoFactory;
delete include.daoFactory;
}
conformOptions(include);
}
return include;
});
};
var validateIncludedElements = function(options, tableNames) {
tableNames = tableNames || {};
options.includeNames = [];
options.includeMap = {};
options.hasSingleAssociation = false;
options.hasMultiAssociation = false;
// validate all included elements
var includes = options.include;
for (var index = 0; index < includes.length; index++) {
var include = includes[index];
if (include.all) {
includes.splice(index, 1);
index--;
validateIncludedAllElement.call(this, includes, include);
continue;
}
include = includes[index] = validateIncludedElement.call(this, include, tableNames);
var include = includes[index] = validateIncludedElement.call(this, includes[index], tableNames);
include.parent = options;
// associations that are required or have a required child as is not a ?:M association are candidates for the subquery
......@@ -1742,7 +1806,6 @@ module.exports = (function() {
options.hasIncludeRequired = options.hasIncludeRequired || include.hasIncludeRequired || !!include.required;
}
};
Model.$validateIncludedElements = validateIncludedElements;
var validateIncludedElement = function(include, tableNames) {
if (!include.hasOwnProperty('model') && !include.hasOwnProperty('association')) {
......@@ -1822,7 +1885,29 @@ module.exports = (function() {
}
};
var validateIncludedAllElement = function(includes, include) {
var expandIncludeAll = function(options) {
var includes = options.include;
if (!includes) {
return;
}
for (var index = 0; index < includes.length; index++) {
var include = includes[index];
if (include.all) {
includes.splice(index, 1);
index--;
expandIncludeAllElement.call(this, includes, include);
}
}
Utils._.forEach(includes, function(include) {
expandIncludeAll.call(include.model, include);
});
};
var expandIncludeAllElement = function(includes, include) {
// check 'all' attribute provided is valid
var all = include.all;
delete include.all;
......
......@@ -332,18 +332,18 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
describe('generic', function() {
it('throws an error about unexpected input if include contains a non-object', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ 1 ] })
}).to.throw(Error)
done()
self.Worker.find({ include: [ 1 ] }).catch(function(err) {
expect(err.message).to.equal('Include unexpected. Element has to be either a Model, an Association or an object.');
done()
})
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
self.Worker.find({ include: [ self.Task ] }).catch(function(err) {
expect(err.message).to.equal('Task is not associated to Worker!');
done()
})
})
it('returns the associated worker via task.worker', function(done) {
......@@ -518,10 +518,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Task.find({ include: [ self.Worker ] })
}).to.throw(Error, 'Worker is not associated to Task!')
done()
self.Task.find({ include: [ self.Worker ] }).catch(function(err) {
expect(err.message).to.equal('Worker is not associated to Task!');
done()
})
})
it('returns the associated task via worker.task', function(done) {
......@@ -577,10 +577,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
describe('hasOne with alias', function() {
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
self.Worker.find({ include: [ self.Task ] }).catch(function(err) {
expect(err.message).to.equal('Task is not associated to Worker!');
done()
})
})
describe('alias', function() {
......@@ -596,10 +596,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
self.Worker.find({ include: [ { daoFactory: self.Task, as: 'Work' } ] }).catch(function(err) {
expect(err.message).to.equal('Task (Work) is not associated to Worker!');
done()
})
})
it('returns the associated task via worker.task', function(done) {
......@@ -657,10 +657,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Task.find({ include: [ self.Worker ] })
}).to.throw(Error, 'Worker is not associated to Task!')
done()
self.Task.find({ include: [ self.Worker ] }).catch(function(err) {
expect(err.message).to.equal('Worker is not associated to Task!');
done()
})
})
it('returns the associated tasks via worker.tasks', function(done) {
......@@ -759,10 +759,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
describe('hasMany with alias', function() {
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
self.Worker.find({ include: [ self.Task ] }).catch(function(err) {
expect(err.message).to.equal('Task is not associated to Worker!');
done()
})
})
describe('alias', function() {
......@@ -778,10 +778,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
self.Worker.find({ include: [ { daoFactory: self.Task, as: 'Work' } ] }).catch(function(err) {
expect(err.message).to.equal('Task (Work) is not associated to Worker!');
done()
})
})
it('returns the associated task via worker.task', function(done) {
......
......@@ -575,18 +575,18 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('throws an error about unexpected input if include contains a non-object', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ 1 ] })
}).to.throw(Error)
done()
self.Worker.all({ include: [ 1 ] }).catch(function(err) {
expect(err.message).to.equal('Include unexpected. Element has to be either a Model, an Association or an object.');
done()
})
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ self.Task ] })
}).to.throw(Error, 'TaskBelongsTo is not associated to Worker!')
done()
self.Worker.all({ include: [ self.Task ] }).catch(function(err) {
expect(err.message).to.equal('TaskBelongsTo is not associated to Worker!');
done()
})
})
it('returns the associated worker via task.worker', function(done) {
......@@ -627,10 +627,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Task.all({ include: [ self.Worker ] })
}).to.throw(Error, 'Worker is not associated to TaskHasOne!')
done()
self.Task.all({ include: [ self.Worker ] }).catch(function(err) {
expect(err.message).to.equal('Worker is not associated to TaskHasOne!');
done()
})
})
it('returns the associated task via worker.task', function(done) {
......@@ -672,18 +672,18 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
self.Worker.all({ include: [ self.Task ] }).catch(function(err) {
expect(err.message).to.equal('Task is not associated to Worker!');
done()
})
})
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
self.Worker.all({ include: [ { daoFactory: self.Task, as: 'Work' } ] }).catch(function(err) {
expect(err.message).to.equal('Task (Work) is not associated to Worker!');
done()
})
})
it('returns the associated task via worker.task', function(done) {
......@@ -735,10 +735,10 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Task.findAll({ include: [ self.Worker ] })
}).to.throw(Error, 'worker is not associated to task!')
done()
self.Task.findAll({ include: [ self.Worker ] }).catch(function(err) {
expect(err.message).to.equal('worker is not associated to task!');
done()
})
})
it('returns the associated tasks via worker.tasks', function(done) {
......@@ -780,18 +780,18 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.findAll({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
self.Worker.findAll({ include: [ self.Task ] }).catch(function(err) {
expect(err.message).to.equal('Task is not associated to Worker!');
done()
})
})
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.findAll({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
self.Worker.findAll({ include: [ { daoFactory: self.Task, as: 'Work' } ] }).catch(function(err) {
expect(err.message).to.equal('Task (Work) is not associated to Worker!');
done()
})
})
it('returns the associated task via worker.task', function(done) {
......
This diff could not be displayed because it is too large.
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!