From b477d1699280a21656c324e3298fc3c69cbfb5f6 Mon Sep 17 00:00:00 2001 From: Nikita Lutsenko Date: Wed, 24 Feb 2016 00:05:03 -0800 Subject: [PATCH 1/5] Do not do any setup for beforeSave trigger if none is set for this className. --- src/RestWrite.js | 5 +++++ src/triggers.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/RestWrite.js b/src/RestWrite.js index bc2b91bac8..ff9ffa17eb 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -111,6 +111,11 @@ RestWrite.prototype.validateSchema = function() { // Runs any beforeSave triggers against this operation. // Any change leads to our data being mutated. RestWrite.prototype.runBeforeTrigger = function() { + // Avoid doing any setup for triggers if there is no 'beforeSave' trigger for this class. + if (!triggers.triggerExists(this.className, triggers.Types.beforeSave)) { + return Promise.resolve(); + } + // Cloud code gets a bit of extra data for its objects var extraData = {className: this.className}; if (this.query && this.query.objectId) { diff --git a/src/triggers.js b/src/triggers.js index fadb03f085..8c9d068dd0 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -18,6 +18,10 @@ var getTrigger = function(className, triggerType) { return undefined; }; +function triggerExists(className: string, type: string): boolean { + return (getTrigger(className, type) != undefined); +} + var getRequestObject = function(triggerType, auth, parseObject, originalParseObject) { var request = { triggerName: triggerType, @@ -96,5 +100,6 @@ module.exports = { getRequestObject: getRequestObject, inflate: inflate, maybeRunTrigger: maybeRunTrigger, + triggerExists: triggerExists, Types: Types }; From 7a14695da88f930094e637b43d49b729c58edc41 Mon Sep 17 00:00:00 2001 From: Nikita Lutsenko Date: Wed, 24 Feb 2016 01:23:24 -0800 Subject: [PATCH 2/5] Restrict triggers on _Session class. --- src/index.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 0987318419..2b3fda1102 100644 --- a/src/index.js +++ b/src/index.js @@ -211,24 +211,35 @@ function addParseCloud() { afterDelete: {} }; + function validateClassNameForTriggers(className) { + const restrictedClassNames = [ '_Session' ]; + if (restrictedClassNames.indexOf(className) != -1) { + throw `Triggers are not supported for ${className} class.`; + } + } + Parse.Cloud.define = function(functionName, handler, validationHandler) { Parse.Cloud.Functions[functionName] = handler; Parse.Cloud.Validators[functionName] = validationHandler; }; Parse.Cloud.beforeSave = function(parseClass, handler) { - var className = getClassName(parseClass); + let className = getClassName(parseClass); + validateClassNameForTriggers(className); Parse.Cloud.Triggers.beforeSave[className] = handler; }; Parse.Cloud.beforeDelete = function(parseClass, handler) { - var className = getClassName(parseClass); + let className = getClassName(parseClass); + validateClassNameForTriggers(className); Parse.Cloud.Triggers.beforeDelete[className] = handler; }; Parse.Cloud.afterSave = function(parseClass, handler) { - var className = getClassName(parseClass); + let className = getClassName(parseClass); + validateClassNameForTriggers(className); Parse.Cloud.Triggers.afterSave[className] = handler; }; Parse.Cloud.afterDelete = function(parseClass, handler) { - var className = getClassName(parseClass); + let className = getClassName(parseClass); + validateClassNameForTriggers(className); Parse.Cloud.Triggers.afterDelete[className] = handler; }; Parse.Cloud.httpRequest = httpRequest; From 39dce71e4ffcb9c4d6910d1176f7c3edd353cea3 Mon Sep 17 00:00:00 2001 From: Nikita Lutsenko Date: Tue, 23 Feb 2016 23:48:45 -0800 Subject: [PATCH 3/5] Add a test to validate pointer mutation in beforeSave hooks. --- spec/ParseAPI.spec.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 3af3663549..0eb0314a99 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -533,6 +533,48 @@ describe('miscellaneous', function() { done(); }); }); + + it('pointer mutation properly saves object', done => { + let className = 'GameScore'; + + Parse.Cloud.beforeSave(className, (req, res) => { + let object = req.object; + expect(object instanceof Parse.Object).toBeTruthy(); + + let child = object.get('child'); + expect(child instanceof Parse.Object).toBeTruthy(); + child.set('a', 'b'); + child.save().then(() => { + res.success(); + }); + }); + + let obj = new Parse.Object(className); + obj.set('foo', 'bar'); + + let child = new Parse.Object('Child'); + child.save().then(() => { + obj.set('child', child); + return obj.save(); + }).then(() => { + let query = new Parse.Query(className); + query.include('child'); + return query.get(obj.id).then(objAgain => { + expect(objAgain.get('foo')).toEqual('bar'); + + let childAgain = objAgain.get('child'); + expect(childAgain instanceof Parse.Object).toBeTruthy(); + expect(childAgain.get('a')).toEqual('b'); + + return Promise.resolve(); + }); + }).then(() => { + done(); + }, error => { + fail(error); + done(); + }); + }); }); it('test afterSave get full object on create and update', function(done) { From 74289f9ce56ce77e470eebd88bef3be626ff5490 Mon Sep 17 00:00:00 2001 From: Nikita Lutsenko Date: Wed, 24 Feb 2016 00:00:25 -0800 Subject: [PATCH 4/5] Use saveJSON instead of full JSON to avoid expansion of pointers. --- src/triggers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/triggers.js b/src/triggers.js index 8c9d068dd0..a05f0b659d 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -56,7 +56,7 @@ var getResponseObject = function(request, resolve, reject) { success: function() { var response = {}; if (request.triggerName === Types.beforeSave) { - response['object'] = request.object.toJSON(); + response['object'] = request.object._getSaveJSON(); } return resolve(response); }, From ae82f33118ddd7f36c824a6b0b5616d82f1b5a19 Mon Sep 17 00:00:00 2001 From: Nikita Lutsenko Date: Wed, 24 Feb 2016 00:05:59 -0800 Subject: [PATCH 5/5] Inflate all data as dirty for new ParseObject in beforeSave. --- src/RestWrite.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/RestWrite.js b/src/RestWrite.js index ff9ffa17eb..ed336cace5 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -123,17 +123,12 @@ RestWrite.prototype.runBeforeTrigger = function() { } let originalObject = null; - let updatedObject = null; + let updatedObject = triggers.inflate(extraData, this.originalData); if (this.query && this.query.objectId) { // This is an update for existing object. originalObject = triggers.inflate(extraData, this.originalData); - updatedObject = triggers.inflate(extraData, this.originalData); - updatedObject.set(Parse._decode(undefined, this.data)); - } else { - // This is create of an object, so no original object exists. - // TODO: (nlutsenko) Use the same flow as for creation, when _Session triggers support is removed. - updatedObject = triggers.inflate(extraData, this.data); } + updatedObject.set(Parse._decode(undefined, this.data)); return Promise.resolve().then(() => { return triggers.maybeRunTrigger(