From ba545fbf590a64d6fe87a7616f58a149ad299d86 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Wed, 16 Mar 2016 19:33:04 -0400 Subject: [PATCH 1/3] Returns full modifications on PUT --- spec/ParseAPI.spec.js | 44 +++++++++++++++++++++++++++ src/Controllers/DatabaseController.js | 17 +++++++---- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 92e3073277..4cf3f7a598 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -868,6 +868,50 @@ describe('miscellaneous', function() { }); }); + it('should return the updated fields on PUT', (done) => { + let obj = new Parse.Object('GameScore'); + obj.save({a:'hello', c: 1, d: ['1'], e:['1'], f:['1','2']}).then(( ) => { + var headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Installation-Id': 'yolo' + }; + request.put({ + headers: headers, + url: 'http://localhost:8378/1/classes/GameScore/'+obj.id, + body: JSON.stringify({ + a: 'b', + c: {"__op":"Increment","amount":2}, + d: {"__op":"Add", objects: ['2']}, + e: {"__op":"AddUnique", objects: ['1', '2']}, + f: {"__op":"Remove", objects: ['2']}, + selfThing: {"__type":"Pointer","className":"GameScore","objectId":obj.id}, + }) + }, (error, response, body) => { + body = JSON.parse(body); + expect(body.a).toBeUndefined(); + expect(body.c).toEqual(3); // 2+1 + expect(body.d.length).toBe(2); + expect(body.d.indexOf('1') > -1).toBe(true); + expect(body.d.indexOf('2') > -1).toBe(true); + expect(body.e.length).toBe(2); + expect(body.e.indexOf('1') > -1).toBe(true); + expect(body.e.indexOf('2') > -1).toBe(true); + expect(body.f.length).toBe(1); + expect(body.f.indexOf('1') > -1).toBe(true); + // return nothing on other self + expect(body.selfThing).toBeUndefined(); + // updatedAt is always set + expect(body.updatedAt).not.toBeUndefined(); + done(); + }); + }).fail((err) => { + fail('Should not fail'); + done(); + }) + }) + it('test cloud function error handling', (done) => { // Register a function which will fail Parse.Cloud.define('willFail', (req, res) => { diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 3e85eca0e6..51f18811d2 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -139,6 +139,8 @@ DatabaseController.prototype.untransformObject = function( // one of the provided strings must provide the caller with // write permissions. DatabaseController.prototype.update = function(className, query, update, options) { + + const originalUpdate = update; // Make a copy of the object, so we don't mutate the incoming data. update = deepcopy(update); @@ -179,12 +181,15 @@ DatabaseController.prototype.update = function(className, query, update, options } let response = {}; - let inc = mongoUpdate['$inc']; - if (inc) { - Object.keys(inc).forEach(key => { + Object.keys(originalUpdate).forEach(key => { + let keyUpdate = originalUpdate[key]; + // determine if that was an op + if (keyUpdate && typeof keyUpdate === 'object' && keyUpdate.__op + && ['Add', 'AddUnique', 'Remove', 'Increment'].indexOf(keyUpdate.__op) > -1) { + // only valid ops that produce an actionable result response[key] = result[key]; - }); - } + } + }); return response; }); }; @@ -474,7 +479,7 @@ DatabaseController.prototype.reduceRelationKeys = function(className, query) { DatabaseController.prototype.addInObjectIdsIds = function(ids, query) { if (typeof query.objectId == 'string') { - // Add equality op as we are sure + // Add equality op as we are sure // we had a constraint on that one query.objectId = {'$eq': query.objectId}; } From e1c475512c60931cb7c6a69f2e2624ba68a6e504 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Wed, 16 Mar 2016 23:48:52 -0400 Subject: [PATCH 2/3] Returns updated keys when running with beforeSave --- spec/ParseAPI.spec.js | 13 ++++++++++ src/Controllers/DatabaseController.js | 34 +++++++++++++++++---------- src/RestWrite.js | 17 ++++++++++---- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 4cf3f7a598..0edcc2f49f 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -224,6 +224,19 @@ describe('miscellaneous', function() { }); }); + it('test beforeSave returns value on create and update', (done) => { + var obj = new Parse.Object('BeforeSaveChanged'); + obj.set('foo', 'bing'); + obj.save().then(() => { + expect(obj.get('foo')).toEqual('baz'); + obj.set('foo', 'bar'); + return obj.save().then(() => { + expect(obj.get('foo')).toEqual('baz'); + done(); + }) + }) + }); + it('test afterSave ran and created an object', function(done) { var obj = new Parse.Object('AfterSaveTest'); obj.save(); diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 51f18811d2..7494cc88ff 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -179,21 +179,27 @@ DatabaseController.prototype.update = function(className, query, update, options return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.')); } - - let response = {}; - Object.keys(originalUpdate).forEach(key => { - let keyUpdate = originalUpdate[key]; - // determine if that was an op - if (keyUpdate && typeof keyUpdate === 'object' && keyUpdate.__op - && ['Add', 'AddUnique', 'Remove', 'Increment'].indexOf(keyUpdate.__op) > -1) { - // only valid ops that produce an actionable result - response[key] = result[key]; - } - }); - return response; + return sanitizeDatabaseResult(originalUpdate, result); }); }; +function sanitizeDatabaseResult(originalObject, result) { + let response = {}; + if (!result) { + return Promise.resolve(response); + } + Object.keys(originalObject).forEach(key => { + let keyUpdate = originalObject[key]; + // determine if that was an op + if (keyUpdate && typeof keyUpdate === 'object' && keyUpdate.__op + && ['Add', 'AddUnique', 'Remove', 'Increment'].indexOf(keyUpdate.__op) > -1) { + // only valid ops that produce an actionable result + response[key] = result[key]; + } + }); + return Promise.resolve(response); +} + // Processes relation-updating operations from a REST-format update. // Returns a promise that resolves successfully when these are // processed. @@ -318,6 +324,7 @@ DatabaseController.prototype.destroy = function(className, query, options = {}) // Returns a promise that resolves successfully iff the object saved. DatabaseController.prototype.create = function(className, object, options) { // Make a copy of the object, so we don't mutate the incoming data. + let originalObject = object; object = deepcopy(object); var schema; @@ -338,6 +345,9 @@ DatabaseController.prototype.create = function(className, object, options) { .then(coll => { var mongoObject = transform.transformCreate(schema, className, object); return coll.insertOne(mongoObject); + }) + .then(result => { + return sanitizeDatabaseResult(originalObject, result.ops[0]); }); }; diff --git a/src/RestWrite.js b/src/RestWrite.js index e241a98854..e0f1f5d5ec 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -711,6 +711,12 @@ RestWrite.prototype.runDatabaseOperation = function() { return this.config.database.update( this.className, this.query, this.data, this.runOptions).then((resp) => { resp.updatedAt = this.updatedAt; + if (this.storage['changedByTrigger']) { + resp = Object.keys(this.data).reduce((memo, key) => { + memo[key] = resp[key] || this.data[key]; + return memo; + }, resp); + } this.response = { response: resp }; @@ -725,13 +731,16 @@ RestWrite.prototype.runDatabaseOperation = function() { } // Run a create return this.config.database.create(this.className, this.data, this.runOptions) - .then(() => { - var resp = { + .then((resp) => { + Object.assign(resp, { objectId: this.data.objectId, createdAt: this.data.createdAt - }; + }); if (this.storage['changedByTrigger']) { - Object.assign(resp, this.data); + resp = Object.keys(this.data).reduce((memo, key) => { + memo[key] = resp[key] || this.data[key]; + return memo; + }, resp); } if (this.storage['token']) { resp.sessionToken = this.storage['token']; From e270964b4b07029719b5232dedf7e00af32092d4 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Wed, 16 Mar 2016 23:59:01 -0400 Subject: [PATCH 3/3] adds test for issue #1031 --- spec/ParseAPI.spec.js | 7 +++++++ spec/cloud/main.js | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 0edcc2f49f..1a7eadddf8 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -396,6 +396,13 @@ describe('miscellaneous', function() { }); }); + it('should properly create an object in before save', (done) => { + Parse.Cloud.run('createBeforeSaveChangedObject').then((res) => { + expect(res.get('foo')).toEqual('baz'); + done(); + }); + }) + it('test rest_create_app', function(done) { var appId; Parse._request('POST', 'rest_create_app').then((res) => { diff --git a/spec/cloud/main.js b/spec/cloud/main.js index 396fa86281..0785c0a624 100644 --- a/spec/cloud/main.js +++ b/spec/cloud/main.js @@ -108,3 +108,10 @@ Parse.Cloud.define('echoKeys', function(req, res){ javascriptKey: Parse.javascriptKey }) }); + +Parse.Cloud.define('createBeforeSaveChangedObject', function(req, res){ + var obj = new Parse.Object('BeforeSaveChanged'); + obj.save().then(() => { + res.success(obj); + }) +})