Skip to content

Commit fb0ae8e

Browse files
committed
fix: correct response when revert is used in beforeSave
1 parent 8a126fc commit fb0ae8e

File tree

2 files changed

+100
-28
lines changed

2 files changed

+100
-28
lines changed

spec/CloudCode.spec.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,82 @@ describe('Cloud Code', () => {
14941494
});
14951495
});
14961496

1497+
it('before save can revert fields', async () => {
1498+
await reconfigureServer({ silent: false });
1499+
1500+
Parse.Cloud.beforeSave('TestObject', ({ object }) => {
1501+
object.revert('foo');
1502+
return object;
1503+
});
1504+
1505+
Parse.Cloud.afterSave('TestObject', ({ object }) => {
1506+
expect(object.get('foo')).toBeUndefined();
1507+
return object;
1508+
});
1509+
1510+
const obj = new TestObject();
1511+
obj.set('foo', 'bar');
1512+
await obj.save();
1513+
1514+
expect(obj.get('foo')).toBeUndefined();
1515+
await obj.fetch();
1516+
1517+
expect(obj.get('foo')).toBeUndefined();
1518+
});
1519+
1520+
it('before save can revert fields with existing object', async () => {
1521+
await reconfigureServer({ silent: false });
1522+
1523+
Parse.Cloud.beforeSave(
1524+
'TestObject',
1525+
({ object }) => {
1526+
object.revert('foo');
1527+
return object;
1528+
},
1529+
{
1530+
skipWithMasterKey: true,
1531+
}
1532+
);
1533+
1534+
Parse.Cloud.afterSave(
1535+
'TestObject',
1536+
({ object }) => {
1537+
expect(object.get('foo')).toBe('bar');
1538+
return object;
1539+
},
1540+
{
1541+
skipWithMasterKey: true,
1542+
}
1543+
);
1544+
1545+
const obj = new TestObject();
1546+
obj.set('foo', 'bar');
1547+
await obj.save(null, { useMasterKey: true });
1548+
1549+
expect(obj.get('foo')).toBe('bar');
1550+
obj.set('foo', 'yolo');
1551+
await obj.save();
1552+
expect(obj.get('foo')).toBe('bar');
1553+
});
1554+
1555+
it('should revert in beforeSave', async () => {
1556+
Parse.Cloud.beforeSave('MyObject', ({ object }) => {
1557+
if (!object.existed()) {
1558+
object.set('count', 0);
1559+
return object;
1560+
}
1561+
object.revert('count');
1562+
return object;
1563+
});
1564+
const obj = await new Parse.Object('MyObject').save();
1565+
expect(obj.get('count')).toBe(0);
1566+
obj.set('count', 10);
1567+
await obj.save();
1568+
expect(obj.get('count')).toBe(0);
1569+
await obj.fetch();
1570+
expect(obj.get('count')).toBe(0);
1571+
});
1572+
14971573
it('beforeSave should not sanitize database', async done => {
14981574
const { adapter } = Config.get(Parse.applicationId).database;
14991575
const spy = spyOn(adapter, 'findOneAndUpdate').and.callThrough();

src/RestWrite.js

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ function RestWrite(config, auth, className, query, data, originalData, clientSDK
8181
// Shared SchemaController to be reused to reduce the number of loadSchema() calls per request
8282
// Once set the schemaData should be immutable
8383
this.validSchemaController = null;
84+
this.pendingOps = {};
8485
}
8586

8687
// A convenient method to perform all the steps of processing the
@@ -211,18 +212,11 @@ RestWrite.prototype.runBeforeSaveTrigger = function () {
211212
return Promise.resolve();
212213
}
213214

214-
// Cloud code gets a bit of extra data for its objects
215-
var extraData = { className: this.className };
216-
if (this.query && this.query.objectId) {
217-
extraData.objectId = this.query.objectId;
218-
}
215+
const { originalObject, updatedObject } = this.buildParseObjects();
219216

220-
let originalObject = null;
221-
const updatedObject = this.buildUpdatedObject(extraData);
222-
if (this.query && this.query.objectId) {
223-
// This is an update for existing object.
224-
originalObject = triggers.inflate(extraData, this.originalData);
225-
}
217+
const stateController = Parse.CoreManager.getObjectStateController();
218+
const [pending] = stateController.getPendingOps(updatedObject._getStateIdentifier());
219+
this.pendingOps = { ...pending };
226220

227221
return Promise.resolve()
228222
.then(() => {
@@ -1517,20 +1511,7 @@ RestWrite.prototype.runAfterSaveTrigger = function () {
15171511
return Promise.resolve();
15181512
}
15191513

1520-
var extraData = { className: this.className };
1521-
if (this.query && this.query.objectId) {
1522-
extraData.objectId = this.query.objectId;
1523-
}
1524-
1525-
// Build the original object, we only do this for a update write.
1526-
let originalObject;
1527-
if (this.query && this.query.objectId) {
1528-
originalObject = triggers.inflate(extraData, this.originalData);
1529-
}
1530-
1531-
// Build the inflated object, different from beforeSave, originalData is not empty
1532-
// since developers can change data in the beforeSave.
1533-
const updatedObject = this.buildUpdatedObject(extraData);
1514+
const { originalObject, updatedObject } = this.buildParseObjects();
15341515
updatedObject._handleSaveResponse(this.response.response, this.response.status || 200);
15351516

15361517
this.config.database.loadSchema().then(schemaController => {
@@ -1556,7 +1537,7 @@ RestWrite.prototype.runAfterSaveTrigger = function () {
15561537
)
15571538
.then(result => {
15581539
if (result && typeof result === 'object') {
1559-
this.response.response = result;
1540+
this.response.response = this._updateResponseWithData(result._toFullJSON(), this.data);
15601541
}
15611542
})
15621543
.catch(function (err) {
@@ -1590,7 +1571,13 @@ RestWrite.prototype.sanitizedData = function () {
15901571
};
15911572

15921573
// Returns an updated copy of the object
1593-
RestWrite.prototype.buildUpdatedObject = function (extraData) {
1574+
RestWrite.prototype.buildParseObjects = function () {
1575+
const extraData = { className: this.className, objectId: this.query?.objectId };
1576+
let originalObject;
1577+
if (this.query && this.query.objectId) {
1578+
originalObject = triggers.inflate(extraData, this.originalData);
1579+
}
1580+
15941581
const className = Parse.Object.fromJSON(extraData);
15951582
const readOnlyAttributes = className.constructor.readOnlyAttributes
15961583
? className.constructor.readOnlyAttributes()
@@ -1628,7 +1615,7 @@ RestWrite.prototype.buildUpdatedObject = function (extraData) {
16281615
delete sanitized[attribute];
16291616
}
16301617
updatedObject.set(sanitized);
1631-
return updatedObject;
1618+
return { updatedObject, originalObject };
16321619
};
16331620

16341621
RestWrite.prototype.cleanUserAuthData = function () {
@@ -1648,6 +1635,15 @@ RestWrite.prototype.cleanUserAuthData = function () {
16481635
};
16491636

16501637
RestWrite.prototype._updateResponseWithData = function (response, data) {
1638+
const { updatedObject } = this.buildParseObjects();
1639+
const stateController = Parse.CoreManager.getObjectStateController();
1640+
const [pending] = stateController.getPendingOps(updatedObject._getStateIdentifier());
1641+
for (const key in this.pendingOps) {
1642+
if (!pending[key]) {
1643+
data[key] = this.originalData ? this.originalData[key] : { __op: 'Delete' };
1644+
this.storage.fieldsChangedByTrigger.push(key);
1645+
}
1646+
}
16511647
if (_.isEmpty(this.storage.fieldsChangedByTrigger)) {
16521648
return response;
16531649
}

0 commit comments

Comments
 (0)