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

Commit 78e86efc by Mick Hansen

feat(transaction): provide support for automatically committing or rolling back …

…a transaction based on the resolution of a promise chain (closes #1939)
1 parent 7b5d2c4b
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
- [FEATURE] Added `field` and `name` to the object form of foreign key definitions - [FEATURE] Added `field` and `name` to the object form of foreign key definitions
- [FEATURE] Added support for calling `Promise.done`, thus explicitly ending the promise chain by calling done with no arguments. Done with a function argument still continues the promise chain, to maintain BC. - [FEATURE] Added support for calling `Promise.done`, thus explicitly ending the promise chain by calling done with no arguments. Done with a function argument still continues the promise chain, to maintain BC.
- [FEATURE] Added `scope` to hasMany association definitions, provides default values to association setters/finders [#2268](https://github.com/sequelize/sequelize/pull/2268) - [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) - [BUG] Only try to create indexes which don't already exist. Closes [#2162](https://github.com/sequelize/sequelize/issues/2162)
#### Backwards compatability changes #### Backwards compatability changes
......
...@@ -4,11 +4,11 @@ var Promise = require('sequelize-bluebird') ...@@ -4,11 +4,11 @@ var Promise = require('sequelize-bluebird')
, EventEmitter = require('events').EventEmitter , EventEmitter = require('events').EventEmitter
, proxyEventKeys = ['success', 'error', 'sql'] , proxyEventKeys = ['success', 'error', 'sql']
, Utils = require('./utils') , Utils = require('./utils')
, seen = {} , deprecatedSeen = {}
, deprecated = function(message) { , deprecated = function(message) {
if (seen[message]) return; if (deprecatedSeen[message]) return;
console.warn(message); console.warn(message);
seen[message] = true; deprecatedSeen[message] = true;
}; };
/** /**
......
...@@ -10,7 +10,13 @@ var url = require('url') ...@@ -10,7 +10,13 @@ var url = require('url')
, Transaction = require('./transaction') , Transaction = require('./transaction')
, QueryTypes = require('./query-types') , QueryTypes = require('./query-types')
, sequelizeErrors = require('./errors') , sequelizeErrors = require('./errors')
, Promise = require('./promise'); , Promise = require('./promise')
, deprecatedSeen = {}
, deprecated = function(message) {
if (deprecatedSeen[message]) return;
console.warn(message);
deprecatedSeen[message] = true;
};
/** /**
* This is the main class, the entry point to sequelize. To use it, you just need to import sequelize: * This is the main class, the entry point to sequelize. To use it, you just need to import sequelize:
...@@ -863,6 +869,21 @@ module.exports = (function() { ...@@ -863,6 +869,21 @@ module.exports = (function() {
* }) * })
* ``` * ```
* *
* A syntax for automatically committing or rolling back based on the promise chain resolution is also supported:
*
* ```js
* sequelize.transaction(function (t) { // Note that we use a callback rather than a promise.then()
* return User.find(..., { transaction: t}).then(function (user) {
* return user.updateAttributes(..., { transaction: t});
* });
* }).then(function () {
* // Commited
* }).catch(function (err) {
* // Rolled back
* console.error(err);
* });
* ```
*
* @see {Transaction} * @see {Transaction}
* @param {Object} [options={}] * @param {Object} [options={}]
...@@ -872,14 +893,34 @@ module.exports = (function() { ...@@ -872,14 +893,34 @@ module.exports = (function() {
* @fires error If there is an uncaught error during the transaction * @fires error If there is an uncaught error during the transaction
* @fires success When the transaction has ended (either comitted or rolled back) * @fires success When the transaction has ended (either comitted or rolled back)
*/ */
Sequelize.prototype.transaction = function(options) { Sequelize.prototype.transaction = function(options, autoCallback) {
if (Utils._.any(arguments, Utils._.isFunction)) { if (typeof options === "function") {
throw new Error('DEPRECATION WARNING: This function no longer accepts callbacks. Use sequelize.transaction().then(function (t) {}) instead.'); autoCallback = options;
options = undefined;
} }
var transaction = new Transaction(this, options); var transaction = new Transaction(this, options);
return transaction.prepareEnvironment().return(transaction); if (autoCallback) {
deprecated('Note: When passing a callback to a transaction a promise chain is expected in return, the transaction will be committed or rejected based on the promise chain returned to the callback.');
return new Promise(function (resolve, reject) {
transaction.prepareEnvironment().then(function () {
autoCallback(transaction).then(function (result) {
return transaction.commit().then(function () {
resolve(result);
});
}).catch(function (err) {
transaction.rollback().then(function () {
reject(err);
}, function () {
reject(err);
});
});
}).catch(reject);
});
} else {
return transaction.prepareEnvironment().return(transaction);
}
}; };
Sequelize.prototype.log = function() { Sequelize.prototype.log = function() {
......
...@@ -2,6 +2,7 @@ var chai = require('chai') ...@@ -2,6 +2,7 @@ var chai = require('chai')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/support') , Support = require(__dirname + '/support')
, dialect = Support.getTestDialect() , dialect = Support.getTestDialect()
, Promise = require(__dirname + '/../lib/promise')
, Transaction = require(__dirname + '/../lib/transaction') , Transaction = require(__dirname + '/../lib/transaction')
, sinon = require('sinon'); , sinon = require('sinon');
...@@ -32,6 +33,28 @@ describe(Support.getTestDialectTeaser("Transaction"), function () { ...@@ -32,6 +33,28 @@ describe(Support.getTestDialectTeaser("Transaction"), function () {
}); });
}); });
describe('autoCallback', function () {
it('supports automatically committing', function () {
return this.sequelize.transaction(function (t) {
return Promise.resolve();
});
});
it('supports automatically rolling back with a thrown error', function () {
return this.sequelize.transaction(function (t) {
throw new Error('Yolo');
}).catch(function (err) {
expect(err).to.be.ok;
});
});
it('supports automatically rolling back with a rejection', function () {
return this.sequelize.transaction(function (t) {
return Promise.reject('Swag');
}).catch(function (err) {
expect(err).to.be.ok;
});
});
});
if (dialect !== 'sqlite') { if (dialect !== 'sqlite') {
describe('row locking', function () { describe('row locking', function () {
this.timeout(10000); this.timeout(10000);
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!