Skip to content

Commit e4cfaa6

Browse files
authored
fix(Model.update()): Fix bug in Model.update() inside a transaction (#148)
#144
1 parent 951cad3 commit e4cfaa6

File tree

3 files changed

+78
-3
lines changed

3 files changed

+78
-3
lines changed

lib/model.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ class Model extends Entity {
218218
const key = this.key(id, ancestors, namespace);
219219
const replace = options && options.replace === true;
220220

221+
let internalTransaction = false;
222+
221223
/**
222224
* If options.replace is set to true we don't fetch the entity
223225
* and save the data directly to the specified key, overriding any previous data.
@@ -228,6 +230,7 @@ class Model extends Entity {
228230
}
229231

230232
if (typeof transaction === 'undefined' || transaction === null) {
233+
internalTransaction = true;
231234
transaction = this.gstore.ds.transaction();
232235
return transaction
233236
.run()
@@ -302,7 +305,9 @@ class Model extends Entity {
302305
options.dataloader.clear(key);
303306
}
304307

305-
if (transaction) {
308+
if (internalTransaction) {
309+
// If we created the Transaction instance internally for the update, we commit it
310+
// otherwise we leave the commit() call to the transaction creator
306311
return transaction.commit().then(onTransactionSuccess);
307312
}
308313

@@ -311,7 +316,9 @@ class Model extends Entity {
311316

312317
function onUpdateError(err) {
313318
error = err;
314-
if (transaction) {
319+
if (internalTransaction) {
320+
// If we created the Transaction instance internally for the update, we rollback it
321+
// otherwise we leave the rollback() call to the transaction creator
315322
return transaction.rollback().then(onTransactionError);
316323
}
317324

test/integration/model.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
const chai = require('chai');
66
const sinon = require('sinon');
77
const Chance = require('chance');
8+
const Joi = require('joi');
89
const { Datastore } = require('@google-cloud/datastore');
910
const { argv } = require('yargs');
1011

@@ -291,6 +292,73 @@ describe('Model (Integration Tests)', () => {
291292
});
292293
});
293294

295+
describe('update()', () => {
296+
describe('transaction()', () => {
297+
it('should update entity inside a transaction', () => {
298+
const userSchema = new Schema({
299+
name: { joi: Joi.string().required() },
300+
lastname: { joi: Joi.string() },
301+
password: { joi: Joi.string() },
302+
coins: { joi: Joi.number().integer().min(0) },
303+
email: { joi: Joi.string().email() },
304+
createdAt: { joi: Joi.date() },
305+
access_token: { joi: [Joi.string(), Joi.number()] },
306+
birthyear: { joi: Joi.number().integer().min(1900).max(2013) },
307+
}, { joi: true });
308+
309+
const User = gstore.model('ModelTestsTransaction-User', userSchema);
310+
311+
function transferCoins(fromUser, toUser, amount) {
312+
return new Promise((resolve, reject) => {
313+
const transaction = gstore.transaction();
314+
return transaction.run()
315+
.then(async () => {
316+
await User.update(fromUser.entityKey.id, {
317+
coins: fromUser.coins - amount,
318+
}, null, null, transaction);
319+
320+
await User.update(toUser.entityKey.id, {
321+
coins: toUser.coins + amount,
322+
}, null, null, transaction);
323+
324+
transaction.commit()
325+
.then(async () => {
326+
const [user1, user2] = await User.get([
327+
fromUser.entityKey.id,
328+
toUser.entityKey.id,
329+
], null, null, null, { preserveOrder: true });
330+
expect(user1.name).equal('User1');
331+
expect(user1.coins).equal(0);
332+
expect(user2.name).equal('User2');
333+
expect(user2.coins).equal(1050);
334+
resolve();
335+
}).catch((err) => {
336+
reject(err);
337+
});
338+
}).catch((err) => {
339+
transaction.rollback();
340+
reject(err);
341+
});
342+
});
343+
}
344+
345+
const fromUser = new User({ name: 'User1', coins: 1000 });
346+
const toUser = new User({ name: 'User2', coins: 50 });
347+
348+
return fromUser.save()
349+
.then(({ entityKey }) => {
350+
addKey(entityKey);
351+
return toUser.save();
352+
})
353+
.then(({ entityKey }) => {
354+
addKey(entityKey);
355+
return transferCoins(fromUser, toUser, 1000);
356+
});
357+
});
358+
});
359+
});
360+
361+
294362
describe('hooks', () => {
295363
it('post delete hook should set scope on entity instance', () => {
296364
const schema = new Schema({ name: { type: 'string' } });

test/integration/schema.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describe('Schema (Integration Tests)', () => {
5252
},
5353
});
5454

55-
const User = gstore.model('User', schema);
55+
const User = gstore.model('ModelTestsSchema-User', schema);
5656

5757
const email = chance.email();
5858
const password = chance.string();

0 commit comments

Comments
 (0)