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

Commit 11960f26 by Sushant Committed by GitHub

Fix #3965, before/after upsert hooks (#6165)

* before/after upsert hooks

* using promise.try
1 parent 835d5bfd
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
- [CHANGED] `Sequelize.Promise` is now an independent copy of `bluebird` library [#5974](https://github.com/sequelize/sequelize/issues/5974) - [CHANGED] `Sequelize.Promise` is now an independent copy of `bluebird` library [#5974](https://github.com/sequelize/sequelize/issues/5974)
- [ADDED] before/after Save hook [#2702](https://github.com/sequelize/sequelize/issues/2702) - [ADDED] before/after Save hook [#2702](https://github.com/sequelize/sequelize/issues/2702)
- [ADDED] Remove hooks by reference [#6155](https://github.com/sequelize/sequelize/issues/6155) - [ADDED] Remove hooks by reference [#6155](https://github.com/sequelize/sequelize/issues/6155)
- [ADDED] before/after Upsert hook [#3965](https://github.com/sequelize/sequelize/issues/3965)
## BC breaks: ## BC breaks:
- Range type bounds now default to [postgres default](https://www.postgresql.org/docs/9.5/static/rangetypes.html#RANGETYPES-CONSTRUCT) `[)` (inclusive, exclusive), previously was `()` (exclusive, exclusive) - Range type bounds now default to [postgres default](https://www.postgresql.org/docs/9.5/static/rangetypes.html#RANGETYPES-CONSTRUCT) `[)` (inclusive, exclusive), previously was `()` (exclusive, exclusive)
......
...@@ -22,6 +22,7 @@ For a full list of hooks, see [Hooks API](/api/hooks). ...@@ -22,6 +22,7 @@ For a full list of hooks, see [Hooks API](/api/hooks).
beforeDestroy(instance, options, fn) beforeDestroy(instance, options, fn)
beforeUpdate(instance, options, fn) beforeUpdate(instance, options, fn)
beforeSave(instance, options, fn) beforeSave(instance, options, fn)
beforeUpsert(values, options, fn)
(-) (-)
create create
destroy destroy
...@@ -31,6 +32,7 @@ For a full list of hooks, see [Hooks API](/api/hooks). ...@@ -31,6 +32,7 @@ For a full list of hooks, see [Hooks API](/api/hooks).
afterDestroy(instance, options, fn) afterDestroy(instance, options, fn)
afterUpdate(instance, options, fn) afterUpdate(instance, options, fn)
afterSave(instance, options, fn) afterSave(instance, options, fn)
afterUpsert(created, options, fn)
(6) (6)
afterBulkCreate(instances, options, fn) afterBulkCreate(instances, options, fn)
afterBulkDestroy(options, fn) afterBulkDestroy(options, fn)
......
...@@ -50,6 +50,8 @@ const hookTypes = { ...@@ -50,6 +50,8 @@ const hookTypes = {
afterUpdate: {params: 2}, afterUpdate: {params: 2},
beforeSave: {params: 2, proxies: ['beforeUpdate', 'beforeCreate']}, beforeSave: {params: 2, proxies: ['beforeUpdate', 'beforeCreate']},
afterSave: {params: 2, proxies: ['afterUpdate', 'afterCreate']}, afterSave: {params: 2, proxies: ['afterUpdate', 'afterCreate']},
beforeUpsert: {params: 2},
afterUpsert: {params: 2},
beforeBulkCreate: {params: 2}, beforeBulkCreate: {params: 2},
afterBulkCreate: {params: 2}, afterBulkCreate: {params: 2},
beforeBulkDestroy: {params: 1}, beforeBulkDestroy: {params: 1},
...@@ -269,13 +271,27 @@ Hooks.hasHooks = Hooks.hasHook; ...@@ -269,13 +271,27 @@ Hooks.hasHooks = Hooks.hasHook;
* @name afterCreate * @name afterCreate
*/ */
/** /**
* A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate`
* @param {String} name * @param {String} name
* @param {Function} fn A callback function that is called with attributes, options * @param {Function} fn A callback function that is called with attributes, options
* @name beforeSave * @name beforeSave
*/ */
/**
* A hook that is run before upserting
* @param {String} name
* @param {Function} fn A callback function that is called with attributes, options
* @name beforeUpsert
*/
/**
* A hook that is run after upserting
* @param {String} name
* @param {Function} fn A callback function that is called with attributes, options
* @name afterUpsert
*/
/** /**
* A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate`
* @param {String} name * @param {String} name
......
...@@ -1964,6 +1964,7 @@ class Model { ...@@ -1964,6 +1964,7 @@ class Model {
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.validate=true] Run validations before the row is inserted * @param {Boolean} [options.validate=true] Run validations before the row is inserted
* @param {Array} [options.fields=Object.keys(this.attributes)] The fields to insert / update. Defaults to all fields * @param {Array} [options.fields=Object.keys(this.attributes)] The fields to insert / update. Defaults to all fields
* @param {Boolean} [options.hooks=true] Run before / after upsert hooks?
* @param {Transaction} [options.transaction] Transaction to run query under * @param {Transaction} [options.transaction] Transaction to run query under
* @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql.
* @param {Boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). * @param {Boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging).
...@@ -1973,7 +1974,9 @@ class Model { ...@@ -1973,7 +1974,9 @@ class Model {
* @return {Promise<created>} Returns a boolean indicating whether the row was created or updated. * @return {Promise<created>} Returns a boolean indicating whether the row was created or updated.
*/ */
static upsert(values, options) { static upsert(values, options) {
options = Utils.cloneDeep(options) || {}; options = Utils._.extend({
hooks: true
}, Utils.cloneDeep(options || {}));
if (!options.fields) { if (!options.fields) {
options.fields = Object.keys(this.attributes); options.fields = Object.keys(this.attributes);
...@@ -2006,7 +2009,17 @@ class Model { ...@@ -2006,7 +2009,17 @@ class Model {
delete updateValues[this.primaryKeyField]; delete updateValues[this.primaryKeyField];
} }
return this.QueryInterface.upsert(this.getTableName(options), insertValues, updateValues, instance.where(), this, options); return Promise.try(() => {
if (options.hooks) {
return this.runHooks('beforeUpsert', values, options);
}
}).then(() => (
this.QueryInterface.upsert(this.getTableName(options), insertValues, updateValues, instance.where(), this, options)
)).tap(result => {
if (options.hooks) {
return this.runHooks('afterUpsert', result, options);
}
});
}); });
} }
......
'use strict';
/* jshint -W030 */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + '/../../../lib/data-types')
, sinon = require('sinon');
if (Support.sequelize.dialect.supports.upserts) {
describe(Support.getTestDialectTeaser('Hooks'), function() {
beforeEach(function() {
this.User = this.sequelize.define('User', {
username: {
type: DataTypes.STRING,
allowNull: false
},
mood: {
type: DataTypes.ENUM,
values: ['happy', 'sad', 'neutral']
}
});
return this.sequelize.sync({ force: true });
});
describe('#upsert', function() {
describe('on success', function() {
it('should run hooks', function() {
var beforeHook = sinon.spy()
, afterHook = sinon.spy();
this.User.beforeUpsert(beforeHook);
this.User.afterUpsert(afterHook);
return this.User.upsert({username: 'Toni', mood: 'happy'}).then(function() {
expect(beforeHook).to.have.been.calledOnce;
expect(afterHook).to.have.been.calledOnce;
});
});
});
describe('on error', function() {
it('should return an error from before', function() {
var beforeHook = sinon.spy()
, afterHook = sinon.spy();
this.User.beforeUpsert(function(values, options) {
beforeHook();
throw new Error('Whoops!');
});
this.User.afterUpsert(afterHook);
return expect(this.User.upsert({username: 'Toni', mood: 'happy'})).to.be.rejected.then(function(err) {
expect(beforeHook).to.have.been.calledOnce;
expect(afterHook).not.to.have.been.called;
});
});
it('should return an error from after', function() {
var beforeHook = sinon.spy()
, afterHook = sinon.spy();
this.User.beforeUpsert(beforeHook);
this.User.afterUpsert(function(user, options) {
afterHook();
throw new Error('Whoops!');
});
return expect(this.User.upsert({username: 'Toni', mood: 'happy'})).to.be.rejected.then(function(err) {
expect(beforeHook).to.have.been.calledOnce;
expect(afterHook).to.have.been.calledOnce;
});
});
});
describe('preserves changes to values', function() {
it('beforeUpsert', function(){
var hookCalled = 0;
var valuesOriginal = { mood: 'sad', username: 'leafninja' };
this.User.beforeUpsert(function(values, options) {
values.mood = 'happy';
hookCalled++;
});
return this.User.upsert(valuesOriginal).then(function() {
expect(valuesOriginal.mood).to.equal('happy');
expect(hookCalled).to.equal(1);
});
});
});
});
});
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!