Skip to content

Commit

Permalink
Merge pull request sequelize#5196 from skleeschulte/master
Browse files Browse the repository at this point in the history
Added validationFailed hook
  • Loading branch information
janmeier committed Feb 21, 2016
2 parents efc432b + bc30943 commit a49a7c1
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 6 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Future
- [ADDED] `beforeCount` hook [#5209](https://github.com/sequelize/sequelize/pull/5209)
- [ADDED] `validationFailed` hook [#1626](https://github.com/sequelize/sequelize/issues/1626)

# 3.19.3
- [FIXED] `updatedAt` and `createdAt` values are now set before validation [#5367](https://github.com/sequelize/sequelize/pull/5367)
Expand Down
4 changes: 3 additions & 1 deletion docs/docs/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ For a full list of hooks, see [Hooks API](/api/hooks).
validate
(3)
afterValidate(instance, options, fn)
- or -
validationFailed(instance, options, error, fn)
(4)
beforeCreate(instance, options, fn)
beforeDestroy(instance, options, fn)
Expand Down Expand Up @@ -159,7 +161,7 @@ The following hooks will emit whenever you're editing a single object

```
beforeValidate
afterValidate
afterValidate or validationFailed
beforeCreate / beforeUpdate / beforeDestroy
afterCreate / afterUpdate / afterDestroy
```
Expand Down
9 changes: 9 additions & 0 deletions lib/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var Utils = require('./utils')
var hookTypes = {
beforeValidate: {params: 2},
afterValidate: {params: 2},
validationFailed: {params: 3},
beforeCreate: {params: 2},
afterCreate: {params: 2},
beforeDestroy: {params: 2},
Expand Down Expand Up @@ -224,6 +225,14 @@ Hooks.hasHooks = Hooks.hasHook;
* @name afterValidate
*/

/**
* A hook that is run when validation fails
* @param {String} name
* @param {Function} fn A callback function that is called with instance, options, error. Error is the
* SequelizeValidationError. If the callback throws an error, it will replace the original validation error.
* @name validationFailed
*/

/**
* A hook that is run before creating a single instance
* @param {String} name
Expand Down
7 changes: 5 additions & 2 deletions lib/instance-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ InstanceValidator.prototype.validate = function() {
* Invoke the Validation sequence:
* - Before Validation Model Hooks
* - Validation
* - After Validation Model Hooks
* - On validation success: After Validation Model Hooks
* - On validation failure: Validation Failed Model Hooks
*
* @return {Promise}
*/
Expand All @@ -89,7 +90,9 @@ InstanceValidator.prototype.hookValidate = function() {
return self.modelInstance.Model.runHooks('beforeValidate', self.modelInstance, self.options).then(function() {
return self.validate().then(function(error) {
if (error) {
throw error;
return self.modelInstance.Model.runHooks('validationFailed', self.modelInstance, self.options, error).then(function(newError) {
throw newError || error;
});
}
});
}).then(function() {
Expand Down
2 changes: 1 addition & 1 deletion lib/sequelize.js
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ Sequelize.prototype.getQueryInterface = function() {
* @param {String} [options.comment]
* @param {String} [options.collate]
* @param {String} [options.initialAutoIncrement] Set the initial AUTO_INCREMENT value for the table in MySQL.
* @param {Object} [options.hooks] An object of hook function that are called before and after certain lifecycle events. The possible hooks are: beforeValidate, afterValidate, beforeBulkCreate, beforeBulkDestroy, beforeBulkUpdate, beforeCreate, beforeDestroy, beforeUpdate, afterCreate, afterDestroy, afterUpdate, afterBulkCreate, afterBulkDestory and afterBulkUpdate. See Hooks for more information about hook functions and their signatures. Each property can either be a function, or an array of functions.
* @param {Object} [options.hooks] An object of hook function that are called before and after certain lifecycle events. The possible hooks are: beforeValidate, afterValidate, validationFailed, beforeBulkCreate, beforeBulkDestroy, beforeBulkUpdate, beforeCreate, beforeDestroy, beforeUpdate, afterCreate, afterDestroy, afterUpdate, afterBulkCreate, afterBulkDestory and afterBulkUpdate. See Hooks for more information about hook functions and their signatures. Each property can either be a function, or an array of functions.
* @param {Object} [options.validate] An object of model wide validations. Validations have access to all model values via `this`. If the validator function takes an argument, it is assumed to be async, and is called with a callback that accepts an optional error.
*
* @return {Model}
Expand Down
38 changes: 36 additions & 2 deletions test/integration/hooks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ var chai = require('chai')
describe(Support.getTestDialectTeaser('Hooks'), function() {
beforeEach(function() {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
username: {
type: DataTypes.STRING,
allowNull: false
},
mood: {
type: DataTypes.ENUM,
values: ['happy', 'sad', 'neutral']
Expand All @@ -36,6 +39,7 @@ describe(Support.getTestDialectTeaser('Hooks'), function() {
describe('#create', function() {
it('should return the user', function() {
this.User.beforeValidate(function(user, options) {
user.username = 'Bob';
user.mood = 'happy';
});

Expand All @@ -57,7 +61,37 @@ describe(Support.getTestDialectTeaser('Hooks'), function() {
throw new Error('Whoops! Changed user.mood!');
});

return expect(this.User.create({mood: 'happy'})).to.be.rejectedWith('Whoops! Changed user.mood!');
return expect(this.User.create({username: 'Toni', mood: 'happy'})).to.be.rejectedWith('Whoops! Changed user.mood!');
});

it('should call validationFailed hook', function() {
var validationFailedHook = sinon.spy();

this.User.validationFailed(validationFailedHook);

return expect(this.User.create({mood: 'happy'})).to.be.rejected.then(function(err) {
expect(validationFailedHook).to.have.been.calledOnce;
});
});

it('should not replace the validation error in validationFailed hook by default', function() {
var validationFailedHook = sinon.stub();

this.User.validationFailed(validationFailedHook);

return expect(this.User.create({mood: 'happy'})).to.be.rejected.then(function(err) {
expect(err.name).to.equal('SequelizeValidationError');
});
});

it('should replace the validation error if validationFailed hook creates a new error', function() {
var validationFailedHook = sinon.stub().throws(new Error('Whoops!'));

this.User.validationFailed(validationFailedHook);

return expect(this.User.create({mood: 'happy'})).to.be.rejected.then(function(err) {
expect(err.message).to.equal('Whoops!');
});
});
});
});
Expand Down

0 comments on commit a49a7c1

Please sign in to comment.