diff --git a/src/ParseObject.js b/src/ParseObject.js index f5939fd92..89846b03b 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -775,7 +775,7 @@ export default class ParseObject { } /** - * Creates a new model with identical attributes to this one. + * Creates a new model with identical attributes to this one, similar to Backbone.Model's clone() * @method clone * @return {Parse.Object} */ @@ -803,6 +803,27 @@ export default class ParseObject { return clone; } + /** + * Creates a new instance of this object. Not to be confused with clone() + * @method newInstance + * @return {Parse.Object} + */ + newInstance(): any { + let clone = new this.constructor(); + if (!clone.className) { + clone.className = this.className; + } + clone.id = this.id; + if (singleInstance) { + // Just return an object with the right id + return clone; + } + + let stateController = CoreManager.getObjectStateController(); + stateController.duplicateState(this._getStateIdentifier(), clone._getStateIdentifier()); + return clone; + } + /** * Returns true if this object has never been saved to Parse. * @method isNew diff --git a/src/UniqueInstanceStateController.js b/src/UniqueInstanceStateController.js index 68ab0c6cf..2f86b0066 100644 --- a/src/UniqueInstanceStateController.js +++ b/src/UniqueInstanceStateController.js @@ -123,6 +123,23 @@ export function enqueueTask(obj: ParseObject, task: () => ParsePromise): ParsePr return state.tasks.enqueue(task); } +export function duplicateState(source: ParseObject, dest: ParseObject): void { + let oldState = initializeState(source); + let newState = initializeState(dest); + for (let key in oldState.serverData) { + newState.serverData[key] = oldState.serverData[key]; + } + for (let index = 0; index < oldState.pendingOps.length; index++) { + for (let key in oldState.pendingOps[index]) { + newState.pendingOps[index][key] = oldState.pendingOps[index][key]; + } + } + for (let key in oldState.objectCache) { + newState.objectCache[key] = oldState.objectCache[key]; + } + newState.existed = oldState.existed; +} + export function clearAllState() { objectState = new WeakMap(); } diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index afcc1643b..0fd8a9a5f 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -1720,6 +1720,20 @@ describe('ObjectController', () => { xhrs[0].onreadystatechange(); }); + + it('can create a new instance of an object', () => { + let o = ParseObject.fromJSON({ + className: 'Clone', + objectId: 'C12', + }); + let o2 = o.newInstance(); + expect(o.id).toBe(o2.id); + expect(o.className).toBe(o2.className); + o.set({ valid: true }); + expect(o2.get('valid')).toBe(true); + + expect(o).not.toBe(o2); + }); }); describe('ParseObject (unique instance mode)', () => { @@ -1902,6 +1916,21 @@ describe('ParseObject (unique instance mode)', () => { expect(o.has('name')).toBe(false); expect(o2.has('name')).toBe(true); }); + + it('can create a new instance of an object', () => { + let o = ParseObject.fromJSON({ + className: 'Clone', + objectId: 'C14', + }); + let o2 = o.newInstance(); + expect(o.id).toBe(o2.id); + expect(o.className).toBe(o2.className); + expect(o).not.toBe(o2); + o.set({ valid: true }); + expect(o2.get('valid')).toBe(undefined); + o2 = o.newInstance(); + expect(o2.get('valid')).toBe(true); + }); }); class MyObject extends ParseObject { diff --git a/src/__tests__/ParseUser-test.js b/src/__tests__/ParseUser-test.js index 0d4cdbc44..66776decc 100644 --- a/src/__tests__/ParseUser-test.js +++ b/src/__tests__/ParseUser-test.js @@ -100,6 +100,28 @@ describe('ParseUser', () => { expect(clone.get('sessionToken')).toBe(undefined); }); + it('can create a new instance of a User', () => { + ParseObject.disableSingleInstance(); + let o = ParseObject.fromJSON({ + className: '_User', + objectId: 'U111', + username: 'u111', + email: 'u111@parse.com', + sesionToken: '1313' + }); + let o2 = o.newInstance(); + expect(o.id).toBe(o2.id); + expect(o.className).toBe(o2.className); + expect(o.get('username')).toBe(o2.get('username')); + expect(o.get('sessionToken')).toBe(o2.get('sessionToken')); + expect(o).not.toBe(o2); + o.set({ admin: true }); + expect(o2.get('admin')).toBe(undefined); + o2 = o.newInstance(); + expect(o2.get('admin')).toBe(true); + ParseObject.enableSingleInstance(); + }); + it('makes session tokens readonly', () => { var u = new ParseUser(); expect(u.set.bind(u, 'sessionToken', 'token')).toThrow( diff --git a/src/__tests__/UniqueInstanceStateController-test.js b/src/__tests__/UniqueInstanceStateController-test.js index e7026a4b6..bf7df6deb 100644 --- a/src/__tests__/UniqueInstanceStateController-test.js +++ b/src/__tests__/UniqueInstanceStateController-test.js @@ -420,4 +420,65 @@ describe('UniqueInstanceStateController', () => { closure(); } }); + + it('can duplicate the state of an object', () => { + let obj = new ParseObject(); + UniqueInstanceStateController.setServerData(obj, { counter: 12, name: 'original' }); + let setCount = new ParseOps.SetOp(44); + let setValid = new ParseOps.SetOp(true); + UniqueInstanceStateController.setPendingOp(obj, 'counter', setCount); + UniqueInstanceStateController.setPendingOp(obj, 'valid', setValid); + + let duplicate = new ParseObject(); + UniqueInstanceStateController.duplicateState(obj, duplicate); + expect(UniqueInstanceStateController.getState(duplicate)).toEqual({ + serverData: { counter: 12, name: 'original' }, + pendingOps: [{ counter: setCount, valid: setValid }], + objectCache: {}, + tasks: new TaskQueue(), + existed: false + }); + + UniqueInstanceStateController.setServerData(duplicate, { name: 'duplicate' }); + expect(UniqueInstanceStateController.getState(obj)).toEqual({ + serverData: { counter: 12, name: 'original' }, + pendingOps: [{ counter: setCount, valid: setValid }], + objectCache: {}, + tasks: new TaskQueue(), + existed: false + }); + expect(UniqueInstanceStateController.getState(duplicate)).toEqual({ + serverData: { counter: 12, name: 'duplicate' }, + pendingOps: [{ counter: setCount, valid: setValid }], + objectCache: {}, + tasks: new TaskQueue(), + existed: false + }); + + UniqueInstanceStateController.commitServerChanges(obj, { o: { a: 12 } }); + expect(UniqueInstanceStateController.getState(obj)).toEqual({ + serverData: { counter: 12, name: 'original', o: { a: 12 } }, + pendingOps: [{ counter: setCount, valid: setValid }], + objectCache: { o: '{"a":12}' }, + tasks: new TaskQueue(), + existed: false + }); + expect(UniqueInstanceStateController.getState(duplicate)).toEqual({ + serverData: { counter: 12, name: 'duplicate' }, + pendingOps: [{ counter: setCount, valid: setValid }], + objectCache: {}, + tasks: new TaskQueue(), + existed: false + }); + + let otherDup = new ParseObject(); + UniqueInstanceStateController.duplicateState(obj, otherDup); + expect(UniqueInstanceStateController.getState(otherDup)).toEqual({ + serverData: { counter: 12, name: 'original', o: { a: 12 } }, + pendingOps: [{ counter: setCount, valid: setValid }], + objectCache: { o: '{"a":12}' }, + tasks: new TaskQueue(), + existed: false + }); + }); });