Skip to content

Commit 47d796e

Browse files
authored
fix: afterSave trigger removes pointer in Parse object (parse-community#7913)
1 parent 2e750b7 commit 47d796e

File tree

4 files changed

+75
-4
lines changed

4 files changed

+75
-4
lines changed

spec/CloudCode.spec.js

+26
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,32 @@ describe('Cloud Code', () => {
15981598
expect(obj.get('count')).toBe(0);
15991599
});
16001600

1601+
it('pointer should not be cleared by triggers', async () => {
1602+
Parse.Cloud.afterSave('MyObject', () => {});
1603+
const foo = await new Parse.Object('Test', { foo: 'bar' }).save();
1604+
const obj = await new Parse.Object('MyObject', { foo }).save();
1605+
const foo2 = obj.get('foo');
1606+
expect(foo2.get('foo')).toBe('bar');
1607+
});
1608+
1609+
it('can set a pointer in triggers', async () => {
1610+
Parse.Cloud.beforeSave('MyObject', () => {});
1611+
Parse.Cloud.afterSave(
1612+
'MyObject',
1613+
async ({ object }) => {
1614+
const foo = await new Parse.Object('Test', { foo: 'bar' }).save();
1615+
object.set({ foo });
1616+
await object.save(null, { useMasterKey: true });
1617+
},
1618+
{
1619+
skipWithMasterKey: true,
1620+
}
1621+
);
1622+
const obj = await new Parse.Object('MyObject').save();
1623+
const foo2 = obj.get('foo');
1624+
expect(foo2.get('foo')).toBe('bar');
1625+
});
1626+
16011627
it('beforeSave should not sanitize database', async done => {
16021628
const { adapter } = Config.get(Parse.applicationId).database;
16031629
const spy = spyOn(adapter, 'findOneAndUpdate').and.callThrough();

spec/RestQuery.spec.js

+22
Original file line numberDiff line numberDiff line change
@@ -429,4 +429,26 @@ describe('RestQuery.each', () => {
429429
done();
430430
});
431431
});
432+
433+
it('test afterSave should not affect save response', async () => {
434+
Parse.Cloud.beforeSave('TestObject2', ({ object }) => {
435+
object.set('addedBeforeSave', true);
436+
});
437+
Parse.Cloud.afterSave('TestObject2', ({ object }) => {
438+
object.set('addedAfterSave', true);
439+
object.unset('initialToRemove');
440+
});
441+
const { response } = await rest.create(config, nobody, 'TestObject2', {
442+
initialSave: true,
443+
initialToRemove: true,
444+
});
445+
expect(Object.keys(response).sort()).toEqual([
446+
'addedAfterSave',
447+
'addedBeforeSave',
448+
'createdAt',
449+
'initialToRemove',
450+
'objectId',
451+
'updatedAt',
452+
]);
453+
});
432454
});

src/Controllers/SchemaController.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,15 @@ const defaultColumns: { [string]: SchemaFields } = Object.freeze({
150150
},
151151
});
152152

153+
// fields required for read or write operations on their respective classes.
153154
const requiredColumns = Object.freeze({
154-
_Product: ['productIdentifier', 'icon', 'order', 'title', 'subtitle'],
155-
_Role: ['name', 'ACL'],
155+
read: {
156+
_User: ['username'],
157+
},
158+
write: {
159+
_Product: ['productIdentifier', 'icon', 'order', 'title', 'subtitle'],
160+
_Role: ['name', 'ACL'],
161+
}
156162
});
157163

158164
const invalidColumns = ['length'];
@@ -1269,7 +1275,7 @@ export default class SchemaController {
12691275

12701276
// Validates that all the properties are set for the object
12711277
validateRequiredColumns(className: string, object: any, query: any) {
1272-
const columns = requiredColumns[className];
1278+
const columns = requiredColumns.write[className];
12731279
if (!columns || columns.length == 0) {
12741280
return Promise.resolve(this);
12751281
}
@@ -1600,4 +1606,5 @@ export {
16001606
convertSchemaToAdapterSchema,
16011607
VolatileClassesSchemas,
16021608
SchemaController,
1609+
requiredColumns,
16031610
};

src/RestWrite.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ var ClientSDK = require('./ClientSDK');
1515
import RestQuery from './RestQuery';
1616
import _ from 'lodash';
1717
import logger from './logger';
18+
import { requiredColumns } from './Controllers/SchemaController';
1819

1920
// query and data are both provided in REST API format. So data
2021
// types are encoded by plain old objects.
@@ -1556,7 +1557,7 @@ RestWrite.prototype.runAfterSaveTrigger = function () {
15561557
this.response.response = result;
15571558
} else {
15581559
this.response.response = this._updateResponseWithData(
1559-
(result || updatedObject)._toFullJSON(),
1560+
(result || updatedObject).toJSON(),
15601561
this.data
15611562
);
15621563
}
@@ -1665,6 +1666,21 @@ RestWrite.prototype._updateResponseWithData = function (response, data) {
16651666
this.storage.fieldsChangedByTrigger.push(key);
16661667
}
16671668
}
1669+
const skipKeys = [
1670+
'objectId',
1671+
'createdAt',
1672+
'updatedAt',
1673+
...(requiredColumns.read[this.className] || []),
1674+
];
1675+
for (const key in response) {
1676+
if (skipKeys.includes(key)) {
1677+
continue;
1678+
}
1679+
const value = response[key];
1680+
if (value == null || (value.__type && value.__type === 'Pointer') || data[key] === value) {
1681+
delete response[key];
1682+
}
1683+
}
16681684
if (_.isEmpty(this.storage.fieldsChangedByTrigger)) {
16691685
return response;
16701686
}

0 commit comments

Comments
 (0)