Skip to content

Commit f443834

Browse files
MobileVetdplewisrdhelms
authored
Bug/server 6899/embedded document increment (#1219)
* Add further complexity to test cases when documents changed A bug has been identified when we attempt to increment a value in an embedded document. This could potentially be missed by the current tests because they only have a document with a single value and that is the value that is updated. This commit adds a second property to the embedded document and ensures it is still present after the various function calls * Add tests that demonstrate the issue with incrementing embedded doc Once the object is saved, the response from the save only returns the updated properties on the embedded document. This is then used to REPLACE the embedded document instead of being merged with it. This is incorrect if the operation was an Increment/Decrement/Add * Attempt to resolve embedded document increment bug This change resolves the system when we use it in our code... however it does NOT result in the new tests that were created passing. I am a little confused by that. * Adjust handleSaveResponse conditional handling. Only certain types of objects should be merged using Javascript spread operator. Previous attempt was including Dates... which fails * _handleSaveResponse: Merge response value with original attributes for nested objects Co-authored-by: Diamond Lewis <[email protected]> Co-authored-by: Robby Helms <[email protected]>
1 parent e155079 commit f443834

File tree

4 files changed

+76
-8
lines changed

4 files changed

+76
-8
lines changed

integration/test/ParseObjectTest.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,27 @@ describe('Parse Object', () => {
255255
assert.equal(result.get('objectField').number, 20);
256256
});
257257

258+
it('can increment nested field and retain full object', async () => {
259+
const obj = new TestObject();
260+
obj.set('objectField', { number: 5, letter: 'a' });
261+
assert.equal(obj.get('objectField').number, 5);
262+
assert.equal(obj.get('objectField').letter, 'a');
263+
await obj.save();
264+
265+
obj.increment('objectField.number', 15);
266+
assert.equal(obj.get('objectField').number, 20);
267+
assert.equal(obj.get('objectField').letter, 'a');
268+
await obj.save();
269+
270+
assert.equal(obj.get('objectField').number, 20);
271+
assert.equal(obj.get('objectField').letter, 'a');
272+
273+
const query = new Parse.Query(TestObject);
274+
const result = await query.get(obj.id);
275+
assert.equal(result.get('objectField').number, 20);
276+
assert.equal(result.get('objectField').letter, 'a');
277+
});
278+
258279
it('can increment non existing field', async () => {
259280
const obj = new TestObject();
260281
obj.set('objectField', { number: 5 });

src/ParseObject.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ class ParseObject {
395395
for (attr in pending) {
396396
if (pending[attr] instanceof RelationOp) {
397397
changes[attr] = pending[attr].applyTo(undefined, this, attr);
398-
} else if (!(attr in response)) {
398+
} else if (!(attr in response) && !attr.includes('.')) {
399399
// Only SetOps and UnsetOps should not come back with results
400400
changes[attr] = pending[attr].applyTo(undefined);
401401
}
@@ -407,7 +407,13 @@ class ParseObject {
407407
} else if (attr === 'ACL') {
408408
changes[attr] = new ParseACL(response[attr]);
409409
} else if (attr !== 'objectId') {
410-
changes[attr] = decode(response[attr]);
410+
const val = decode(response[attr]);
411+
if (val && Object.getPrototypeOf(val) === Object.prototype) {
412+
// Update the object by merging in updates w/ old object
413+
changes[attr] = { ...this.attributes[attr], ...val }
414+
} else {
415+
changes[attr] = val
416+
}
411417
if (changes[attr] instanceof UnsetOp) {
412418
changes[attr] = undefined;
413419
}

src/__tests__/ObjectStateMutations-test.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,19 @@ describe('ObjectStateMutations', () => {
120120
});
121121

122122
it('can estimate attributes for nested documents', () => {
123-
const serverData = { objectField: { counter: 10 } };
123+
const serverData = { objectField: { counter: 10, letter: 'a' } };
124124
let pendingOps = [{ 'objectField.counter': new ParseOps.IncrementOp(2) }];
125125
expect(ObjectStateMutations.estimateAttributes(serverData, pendingOps, 'someClass', 'someId')).toEqual({
126126
objectField: {
127-
counter: 12
127+
counter: 12,
128+
letter: 'a'
128129
},
129130
});
130131
pendingOps = [{ 'objectField.counter': new ParseOps.SetOp(20) }];
131132
expect(ObjectStateMutations.estimateAttributes(serverData, pendingOps, 'someClass', 'someId')).toEqual({
132133
objectField: {
133-
counter: 20
134+
counter: 20,
135+
letter: 'a'
134136
},
135137
});
136138
});

src/__tests__/ParseObject-test.js

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -610,20 +610,21 @@ describe('ParseObject', () => {
610610
o._finishFetch({
611611
objectId: 'setNested',
612612
objectField: {
613-
number: 5
613+
number: 5,
614+
letter: 'a'
614615
},
615616
otherField: {},
616617
});
617618

618619
expect(o.attributes).toEqual({
619-
objectField: { number: 5 },
620+
objectField: { number: 5, letter: 'a' },
620621
otherField: {},
621622
});
622623
o.set('otherField', { hello: 'world' });
623624
o.set('objectField.number', 20);
624625

625626
expect(o.attributes).toEqual({
626-
objectField: { number: 20 },
627+
objectField: { number: 20, letter: 'a' },
627628
otherField: { hello: 'world' },
628629
});
629630
expect(o.op('objectField.number') instanceof SetOp).toBe(true);
@@ -634,6 +635,44 @@ describe('ParseObject', () => {
634635
});
635636
});
636637

638+
it('can increment a nested field', () => {
639+
const o = new ParseObject('Person');
640+
o._finishFetch({
641+
objectId: 'incNested',
642+
objectField: {
643+
number: 5,
644+
letter: 'a'
645+
},
646+
});
647+
648+
expect(o.attributes).toEqual({
649+
objectField: { number: 5, letter: 'a' },
650+
});
651+
o.increment('objectField.number');
652+
653+
expect(o.attributes).toEqual({
654+
objectField: { number: 6, letter: 'a' },
655+
});
656+
expect(o.op('objectField.number') instanceof IncrementOp).toBe(true);
657+
expect(o.dirtyKeys()).toEqual(['objectField.number', 'objectField']);
658+
expect(o._getSaveJSON()).toEqual({
659+
'objectField.number': {
660+
"__op": "Increment",
661+
"amount": 1
662+
},
663+
});
664+
665+
// Nested objects only return values changed
666+
o._handleSaveResponse({
667+
objectId: 'incNested',
668+
objectField: {
669+
number: 6
670+
}
671+
})
672+
expect(o.get('objectField').number).toEqual(6)
673+
expect(o.get('objectField').letter).toEqual('a')
674+
});
675+
637676
it('ignore set nested field on new object', () => {
638677
const o = new ParseObject('Person');
639678
o.set('objectField.number', 20);

0 commit comments

Comments
 (0)