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

Commit 0cacb9f5 by Mick Hansen

Merge pull request #4544 from alekbarszczewski/sync-hooks

Added before/afterSync and before/afterBulkSync hooks
2 parents 44d2dd90 3cc77993
# Next
- [ADDED] Expose Association constructor as `Sequelize.Association`
- [ADDED] beforeSync/afterSync/beforeBulkSync/afterBulksync hooks [#4479](https://github.com/sequelize/sequelize/issues/4479)
- [FIXED] Calling set with dot.separated key on a JSON/JSONB attribute will not flag the entire object as changed [#4379](https://github.com/sequelize/sequelize/pull/4379)
- [FIXED] instances returned from `bulkCreate` now has `isNewRecord: false` and should be updateable if using `returning: true` with dialects that support it.
- [FIXED] Find with Include with a where clause generates wrong SQL [#3940](https://github.com/sequelize/sequelize/issues/3940)
......
......@@ -62,7 +62,11 @@ var hookTypes = {
beforeDefine: {params: 2, sync: true},
afterDefine: {params: 1, sync: true},
beforeInit: {params: 2, sync: true},
afterInit: {params: 1, sync: true}
afterInit: {params: 1, sync: true},
beforeSync: {params: 1},
afterSync: {params: 1},
beforeBulkSync: {params: 1},
afterBulkSync: {params: 1}
};
var hookAliases = {
......@@ -399,6 +403,34 @@ Hooks.hasHooks = Hooks.hasHook;
* @name afterInit
*/
/**
* A hook that is run before Model.sync call
* @param {String} name
* @param {Function} fn A callback function that is called with options passed to Model.sync
* @name beforeSync
*/
/**
* A hook that is run after Model.sync call
* @param {String} name
* @param {Function} fn A callback function that is called with options passed to Model.sync
* @name afterSync
*/
/**
* A hook that is run before sequelize.sync call
* @param {String} name
* @param {Function} fn A callback function that is called with options passed to sequelize.sync
* @name beforeBulkSync
*/
/**
* A hook that is run after sequelize.sync call
* @param {String} name
* @param {Function} fn A callback function that is called with options passed to sequelize.sync
* @name afterBulkSync
*/
module.exports = {
hooks: hookTypes,
hookAliases: hookAliases,
......
......@@ -962,12 +962,18 @@ Model.prototype.removeAttribute = function(attribute) {
* @return {Promise<this>}
*/
Model.prototype.sync = function(options) {
options = Utils._.extend({}, this.options, options || {});
options = options || {};
options.hooks = options.hooks === undefined ? true : !!options.hooks;
options = Utils._.extend({}, this.options, options);
var self = this
, attributes = this.tableAttributes;
return Promise.try(function () {
if (options.hooks) {
return self.runHooks('beforeSync', options);
}
}).then(function () {
if (options.force) {
return self.drop(options);
}
......@@ -988,6 +994,10 @@ Model.prototype.sync = function(options) {
return Promise.map(indexes, function (index) {
return self.QueryInterface.addIndex(self.getTableName(options), _.assign({logging: options.logging}, index), self.tableName);
});
}).then(function () {
if (options.hooks) {
return self.runHooks('afterSync', options);
}
}).return(this);
};
......
......@@ -875,27 +875,32 @@ Sequelize.prototype.dropAllSchemas = function(options) {
* @param {RegEx} [options.match] Match a regex against the database name before syncing, a safety check for cases where force: true is used in tests but not live code
* @param {Boolean|function} [options.logging=console.log] A function that logs sql queries, or false for no logging
* @param {String} [options.schema='public'] The schema that the tables should be created in. This can be overriden for each table in sequelize.define
* @param {Boolean} [options.hooks=true] If hooks is true then beforeSync, afterSync, beforBulkSync, afterBulkSync hooks will be called
* @return {Promise}
*/
Sequelize.prototype.sync = function(options) {
var self = this;
options = Utils._.defaults(options || {}, this.options.sync, this.options);
options = options || {};
options.hooks = options.hooks === undefined ? true : !!options.hooks;
options.logging = options.logging === undefined ? false : options.logging;
options = Utils._.defaults(options, this.options.sync, this.options);
if (options.match) {
if (!options.match.test(this.config.database)) {
return Promise.reject('Database does not match sync match parameter');
}
}
var when;
if (options.force) {
when = this.drop(options);
} else {
when = Promise.resolve();
}
return when.then(function() {
return Promise.try(function () {
if (options.hooks) {
return self.runHooks('beforeBulkSync', options);
}
}).then(function () {
if (options.force) {
return self.drop(options);
}
}).then(function() {
var models = [];
// Topologically sort by foreign key constraints to give us an appropriate
......@@ -910,9 +915,12 @@ Sequelize.prototype.sync = function(options) {
return Promise.each(models, function(model) {
return model.sync(options);
// return the sequelize instance
}).return(self);
});
});
}).then(function () {
if (options.hooks) {
return self.runHooks('afterBulkSync', options);
}
}).return(self);
};
/**
......
......@@ -2133,4 +2133,156 @@ describe(Support.getTestDialectTeaser('Hooks'), function() {
});
});
});
describe('Model#sync', function() {
describe('on success', function() {
it('should run hooks', function() {
var beforeHook = sinon.spy()
, afterHook = sinon.spy();
this.User.beforeSync(beforeHook);
this.User.afterSync(afterHook);
return this.User.sync().then(function() {
expect(beforeHook).to.have.been.calledOnce;
expect(afterHook).to.have.been.calledOnce;
});
});
it('should not run hooks when "hooks = false" option passed', function() {
var beforeHook = sinon.spy()
, afterHook = sinon.spy();
this.User.beforeSync(beforeHook);
this.User.afterSync(afterHook);
return this.User.sync({ hooks: false }).then(function() {
expect(beforeHook).to.not.have.been.called;
expect(afterHook).to.not.have.been.called;
});
});
});
describe('on error', function() {
it('should return an error from before', function() {
var beforeHook = sinon.spy()
, afterHook = sinon.spy();
this.User.beforeSync(function(options) {
beforeHook();
throw new Error('Whoops!');
});
this.User.afterSync(afterHook);
return expect(this.User.sync()).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.beforeSync(beforeHook);
this.User.afterSync(function(options) {
afterHook();
throw new Error('Whoops!');
});
return expect(this.User.sync()).to.be.rejected.then(function(err) {
expect(beforeHook).to.have.been.calledOnce;
expect(afterHook).to.have.been.calledOnce;
});
});
});
});
describe('sequelize#sync', function() {
describe('on success', function() {
it('should run hooks', function() {
var beforeHook = sinon.spy()
, afterHook = sinon.spy()
, modelBeforeHook = sinon.spy()
, modelAfterHook = sinon.spy();
this.sequelize.beforeBulkSync(beforeHook);
this.User.beforeSync(modelBeforeHook);
this.User.afterSync(modelAfterHook);
this.sequelize.afterBulkSync(afterHook);
return this.sequelize.sync().then(function() {
expect(beforeHook).to.have.been.calledOnce;
expect(modelBeforeHook).to.have.been.calledOnce;
expect(modelAfterHook).to.have.been.calledOnce;
expect(afterHook).to.have.been.calledOnce;
});
});
it('should not run hooks if "hooks = false" option passed', function() {
var beforeHook = sinon.spy()
, afterHook = sinon.spy()
, modelBeforeHook = sinon.spy()
, modelAfterHook = sinon.spy();
this.sequelize.beforeBulkSync(beforeHook);
this.User.beforeSync(modelBeforeHook);
this.User.afterSync(modelAfterHook);
this.sequelize.afterBulkSync(afterHook);
return this.sequelize.sync({ hooks: false }).then(function() {
expect(beforeHook).to.not.have.been.called;
expect(modelBeforeHook).to.not.have.been.called;
expect(modelAfterHook).to.not.have.been.called;
expect(afterHook).to.not.have.been.called;
});
});
afterEach(function() {
this.sequelize.options.hooks = {};
});
});
describe('on error', function() {
it('should return an error from before', function() {
var beforeHook = sinon.spy()
, afterHook = sinon.spy();
this.sequelize.beforeBulkSync(function(options) {
beforeHook();
throw new Error('Whoops!');
});
this.sequelize.afterBulkSync(afterHook);
return expect(this.sequelize.sync()).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.sequelize.beforeBulkSync(beforeHook);
this.sequelize.afterBulkSync(function(options) {
afterHook();
throw new Error('Whoops!');
});
return expect(this.sequelize.sync()).to.be.rejected.then(function(err) {
expect(beforeHook).to.have.been.calledOnce;
expect(afterHook).to.have.been.calledOnce;
});
});
afterEach(function() {
this.sequelize.options.hooks = {};
});
});
});
});
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!