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

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 # Next
- [ADDED] Expose Association constructor as `Sequelize.Association` - [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] 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] 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) - [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 = { ...@@ -62,7 +62,11 @@ var hookTypes = {
beforeDefine: {params: 2, sync: true}, beforeDefine: {params: 2, sync: true},
afterDefine: {params: 1, sync: true}, afterDefine: {params: 1, sync: true},
beforeInit: {params: 2, 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 = { var hookAliases = {
...@@ -399,6 +403,34 @@ Hooks.hasHooks = Hooks.hasHook; ...@@ -399,6 +403,34 @@ Hooks.hasHooks = Hooks.hasHook;
* @name afterInit * @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 = { module.exports = {
hooks: hookTypes, hooks: hookTypes,
hookAliases: hookAliases, hookAliases: hookAliases,
......
...@@ -962,12 +962,18 @@ Model.prototype.removeAttribute = function(attribute) { ...@@ -962,12 +962,18 @@ Model.prototype.removeAttribute = function(attribute) {
* @return {Promise<this>} * @return {Promise<this>}
*/ */
Model.prototype.sync = function(options) { 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 var self = this
, attributes = this.tableAttributes; , attributes = this.tableAttributes;
return Promise.try(function () { return Promise.try(function () {
if (options.hooks) {
return self.runHooks('beforeSync', options);
}
}).then(function () {
if (options.force) { if (options.force) {
return self.drop(options); return self.drop(options);
} }
...@@ -988,6 +994,10 @@ Model.prototype.sync = function(options) { ...@@ -988,6 +994,10 @@ Model.prototype.sync = function(options) {
return Promise.map(indexes, function (index) { return Promise.map(indexes, function (index) {
return self.QueryInterface.addIndex(self.getTableName(options), _.assign({logging: options.logging}, index), self.tableName); 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); }).return(this);
}; };
......
...@@ -875,27 +875,32 @@ Sequelize.prototype.dropAllSchemas = function(options) { ...@@ -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 {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 {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 {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} * @return {Promise}
*/ */
Sequelize.prototype.sync = function(options) { Sequelize.prototype.sync = function(options) {
var self = this; 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.logging = options.logging === undefined ? false : options.logging;
options = Utils._.defaults(options, this.options.sync, this.options);
if (options.match) { if (options.match) {
if (!options.match.test(this.config.database)) { if (!options.match.test(this.config.database)) {
return Promise.reject('Database does not match sync match parameter'); return Promise.reject('Database does not match sync match parameter');
} }
} }
var when;
return Promise.try(function () {
if (options.hooks) {
return self.runHooks('beforeBulkSync', options);
}
}).then(function () {
if (options.force) { if (options.force) {
when = this.drop(options); return self.drop(options);
} else {
when = Promise.resolve();
} }
}).then(function() {
return when.then(function() {
var models = []; var models = [];
// Topologically sort by foreign key constraints to give us an appropriate // Topologically sort by foreign key constraints to give us an appropriate
...@@ -910,9 +915,12 @@ Sequelize.prototype.sync = function(options) { ...@@ -910,9 +915,12 @@ Sequelize.prototype.sync = function(options) {
return Promise.each(models, function(model) { return Promise.each(models, function(model) {
return model.sync(options); 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() { ...@@ -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!