From 96f0a171d902d75eb9052fed0e26c1b43d3da265 Mon Sep 17 00:00:00 2001 From: "Raschid J.F. Rafaelly" Date: Sun, 4 Aug 2019 00:28:34 -0500 Subject: [PATCH 1/3] feat(ParseObject): Add option `cascadeSave` to save() Closes #864 --- integration/test/ParseObjectTest.js | 46 +++++++++++++++++++++++++++++ src/ParseObject.js | 13 ++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/integration/test/ParseObjectTest.js b/integration/test/ParseObjectTest.js index 47df1124f..6cc85f32d 100644 --- a/integration/test/ParseObjectTest.js +++ b/integration/test/ParseObjectTest.js @@ -934,6 +934,52 @@ describe('Parse Object', () => { }); }); + it('can skip cascade saving as per request', async(done) => { + const Parent = Parse.Object.extend('Parent'); + const Child = Parse.Object.extend('Child'); + + const parent = new Parent(); + const child1 = new Child(); + const child2 = new Child(); + const child3 = await new Child().save(); + + child1.set('name', 'rob'); + child2.set('name', 'sansa'); + child3.set('name', 'john'); + parent.set('children', [child1, child2]); + parent.set('bastard', child3); + + await parent.save(null, { cascadeSave: false }); + const results = await new Parse.Query(Child) + .ascending('name') + .find(); + + assert.equal(results.length, 3); + expect(results[0].get('name')).toBeUndefined(); + assert.equal(results[1].get('name'), 'rob'); + assert.equal(results[2].get('name'), 'sansa'); + + parent.set('dead', true); + child1.set('dead', true); + await parent.save(null); + const rob = await new Parse.Query(Child) + .equalTo('name', 'rob') + .first(); + + expect(rob.get('dead')).toBe(true); + + parent.set('lastname', 'stark'); + child3.set('lastname', 'stark'); + await parent.save(null, { cascadeSave: false }); + const john = await new Parse.Query(Child) + .doesNotExist('lastname') + .first(); + + expect(john.get('lastname')).toBeUndefined(); + + done(); + }); + it('can do two saves at the same time', (done) => { const object = new TestObject(); let firstSave = true; diff --git a/src/ParseObject.js b/src/ParseObject.js index 2f8fa6947..4c8908a51 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -54,6 +54,10 @@ type SaveParams = { body: AttributeMap; }; +type SaveOptions = FullOptions & { + cascadeSave?: boolean +} + const DEFAULT_BATCH_SIZE = 20; // Mapping of class names to constructors, so we can populate objects from the @@ -1131,6 +1135,7 @@ class ParseObject { * be used for this request. *
  • sessionToken: A valid session token, used for making a request on * behalf of a specific user. + *
  • cascadeSave: If `false`, nested objects will not be saved (default is `true`). * *
  • * @@ -1143,6 +1148,7 @@ class ParseObject { * be used for this request. *
  • sessionToken: A valid session token, used for making a request on * behalf of a specific user. + *
  • cascadeSave: If `false`, nested objects will not be saved (default is `true`). * * * @return {Promise} A promise that is fulfilled when the save @@ -1150,8 +1156,8 @@ class ParseObject { */ save( arg1: ?string | { [attr: string]: mixed }, - arg2: FullOptions | mixed, - arg3?: FullOptions + arg2: SaveOptions | mixed, + arg3?: SaveOptions ): Promise { let attrs; let options; @@ -1200,7 +1206,8 @@ class ParseObject { saveOptions.sessionToken = options.sessionToken; } const controller = CoreManager.getObjectController(); - const unsaved = unsavedChildren(this); + let unsaved = unsavedChildren(this); + unsaved = unsaved.filter(o => o._localId || options.cascadeSave !== false); return controller.save(unsaved, saveOptions).then(() => { return controller.save(this, saveOptions); }); From 93c1f05ee06836b6fa6a591586e3da1b1e96931f Mon Sep 17 00:00:00 2001 From: "Raschid J.F. Rafaelly" Date: Mon, 5 Aug 2019 09:41:47 -0500 Subject: [PATCH 2/3] fix(ParseObject): Safe check for unsaved in save() --- src/ParseObject.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ParseObject.js b/src/ParseObject.js index 4c8908a51..3f46ba945 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -1207,7 +1207,7 @@ class ParseObject { } const controller = CoreManager.getObjectController(); let unsaved = unsavedChildren(this); - unsaved = unsaved.filter(o => o._localId || options.cascadeSave !== false); + unsaved = unsaved && unsaved.filter(o => o._localId || options.cascadeSave !== false) || []; return controller.save(unsaved, saveOptions).then(() => { return controller.save(this, saveOptions); }); From b479793828f2656f5138550c87f00b702edb935d Mon Sep 17 00:00:00 2001 From: "Raschid J.F. Rafaelly" Date: Wed, 7 Aug 2019 00:10:52 -0500 Subject: [PATCH 3/3] refactor(ParseObject): `cascadeSave` won't save new objects Nested objects who have not been saved will be ignored when setting `cascadeSave: true`, so save() will throw "Cannot create Pointer to an unsaved ParseObject". --- integration/test/ParseObjectTest.js | 24 ++++++++---------------- src/ParseObject.js | 3 +-- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/integration/test/ParseObjectTest.js b/integration/test/ParseObjectTest.js index 6cc85f32d..d4dddc304 100644 --- a/integration/test/ParseObjectTest.js +++ b/integration/test/ParseObjectTest.js @@ -941,7 +941,7 @@ describe('Parse Object', () => { const parent = new Parent(); const child1 = new Child(); const child2 = new Child(); - const child3 = await new Child().save(); + const child3 = new Child(); child1.set('name', 'rob'); child2.set('name', 'sansa'); @@ -949,32 +949,24 @@ describe('Parse Object', () => { parent.set('children', [child1, child2]); parent.set('bastard', child3); - await parent.save(null, { cascadeSave: false }); - const results = await new Parse.Query(Child) - .ascending('name') - .find(); + expect(parent.save).toThrow(); + let results = await new Parse.Query(Child).find(); + assert.equal(results.length, 0); + await parent.save(null, { cascadeSave: true }); + results = await new Parse.Query(Child).find(); assert.equal(results.length, 3); - expect(results[0].get('name')).toBeUndefined(); - assert.equal(results[1].get('name'), 'rob'); - assert.equal(results[2].get('name'), 'sansa'); parent.set('dead', true); child1.set('dead', true); await parent.save(null); - const rob = await new Parse.Query(Child) - .equalTo('name', 'rob') - .first(); - + const rob = await new Parse.Query(Child).equalTo('name', 'rob').first(); expect(rob.get('dead')).toBe(true); parent.set('lastname', 'stark'); child3.set('lastname', 'stark'); await parent.save(null, { cascadeSave: false }); - const john = await new Parse.Query(Child) - .doesNotExist('lastname') - .first(); - + const john = await new Parse.Query(Child).doesNotExist('lastname').first(); expect(john.get('lastname')).toBeUndefined(); done(); diff --git a/src/ParseObject.js b/src/ParseObject.js index 3f46ba945..7270227f4 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -1206,8 +1206,7 @@ class ParseObject { saveOptions.sessionToken = options.sessionToken; } const controller = CoreManager.getObjectController(); - let unsaved = unsavedChildren(this); - unsaved = unsaved && unsaved.filter(o => o._localId || options.cascadeSave !== false) || []; + const unsaved = options.cascadeSave !== false ? unsavedChildren(this) : null; return controller.save(unsaved, saveOptions).then(() => { return controller.save(this, saveOptions); });