Skip to content

Commit 3fd7e3c

Browse files
committed
feat: Allow saving with custom objectId
1 parent 1abb841 commit 3fd7e3c

File tree

6 files changed

+135
-2
lines changed

6 files changed

+135
-2
lines changed

integration/test/ParseObjectTest.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2055,4 +2055,60 @@ describe('Parse Object', () => {
20552055
expect(obj.get('string')).toBeDefined();
20562056
expect(obj.get('string')).toBeInstanceOf(String);
20572057
});
2058+
2059+
it('allowCustomObjectId', async () => {
2060+
await reconfigureServer({ allowCustomObjectId: true });
2061+
Parse.allowCustomObjectId = true;
2062+
const customId = `${Date.now()}`;
2063+
const object = new Parse.Object('TestObject');
2064+
try {
2065+
await object.save();
2066+
fail();
2067+
} catch (error) {
2068+
expect(error.message).toBe('objectId must not be empty, null or undefined');
2069+
}
2070+
object.id = customId;
2071+
object.set('foo', 'bar');
2072+
await object.save();
2073+
expect(object.id).toBe(customId);
2074+
2075+
const query = new Parse.Query('TestObject');
2076+
const result = await query.get(customId);
2077+
expect(result.get('foo')).toBe('bar');
2078+
expect(result.id).toBe(customId);
2079+
2080+
result.set('foo', 'baz');
2081+
await result.save();
2082+
2083+
const afterSave = await query.get(customId);
2084+
expect(afterSave.get('foo')).toBe('baz');
2085+
Parse.allowCustomObjectId = false;
2086+
});
2087+
2088+
it('allowCustomObjectId saveAll', async () => {
2089+
await reconfigureServer({ allowCustomObjectId: true });
2090+
Parse.allowCustomObjectId = true;
2091+
const customId1 = `${Date.now()}`;
2092+
const customId2 = `${Date.now()}`;
2093+
const obj1 = new TestObject({ foo: 'bar' });
2094+
const obj2 = new TestObject({ foo: 'baz' });
2095+
try {
2096+
await Parse.Object.saveAll([obj1, obj2]);
2097+
fail();
2098+
} catch (error) {
2099+
expect(error.message).toBe('objectId must not be empty, null or undefined');
2100+
}
2101+
obj1.id = customId1;
2102+
obj2.id = customId2;
2103+
await Parse.Object.saveAll([obj1, obj2]);
2104+
expect(obj1.id).toBe(customId1);
2105+
expect(obj2.id).toBe(customId2);
2106+
2107+
const query = new Parse.Query(TestObject);
2108+
const results = await query.find();
2109+
results.forEach(result => {
2110+
expect([customId1, customId2].includes(result.id));
2111+
});
2112+
Parse.allowCustomObjectId = false;
2113+
});
20582114
});

src/CoreManager.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ const config: Config & { [key: string]: mixed } = {
200200
FORCE_REVOCABLE_SESSION: false,
201201
ENCRYPTED_USER: false,
202202
IDEMPOTENCY: false,
203+
ALLOW_CUSTOM_OBJECT_ID: false,
203204
};
204205

205206
function requireMethods(name: string, methods: Array<string>, controller: any) {

src/Parse.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,17 @@ const Parse = {
189189
get idempotency() {
190190
return CoreManager.get('IDEMPOTENCY');
191191
},
192+
193+
/**
194+
* @member {boolean} Parse.allowCustomObjectId
195+
* @static
196+
*/
197+
set allowCustomObjectId(value) {
198+
CoreManager.set('ALLOW_CUSTOM_OBJECT_ID', value);
199+
},
200+
get allowCustomObjectId() {
201+
return CoreManager.get('ALLOW_CUSTOM_OBJECT_ID');
202+
},
192203
};
193204

194205
Parse.ACL = require('./ParseACL').default;

src/ParseObject.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,10 +324,19 @@ class ParseObject {
324324
}
325325

326326
_getSaveParams(): SaveParams {
327-
const method = this.id ? 'PUT' : 'POST';
327+
let method = this.id ? 'PUT' : 'POST';
328328
const body = this._getSaveJSON();
329329
let path = 'classes/' + this.className;
330-
if (this.id) {
330+
if (CoreManager.get('ALLOW_CUSTOM_OBJECT_ID')) {
331+
if (!this.createdAt) {
332+
method = 'POST';
333+
path += '';
334+
body.objectId = this.id;
335+
} else {
336+
method = 'PUT';
337+
path += '/' + this.id;
338+
}
339+
} else if (this.id) {
331340
path += '/' + this.id;
332341
} else if (this.className === '_User') {
333342
path = 'users';
@@ -2362,6 +2371,7 @@ const DefaultController = {
23622371

23632372
const RESTController = CoreManager.getRESTController();
23642373
const stateController = CoreManager.getObjectStateController();
2374+
const allowCustomObjectId = CoreManager.get('ALLOW_CUSTOM_OBJECT_ID');
23652375

23662376
options = options || {};
23672377
options.returnStatus = options.returnStatus || true;
@@ -2384,6 +2394,12 @@ const DefaultController = {
23842394
if (el instanceof ParseFile) {
23852395
filesSaved.push(el.save(options));
23862396
} else if (el instanceof ParseObject) {
2397+
if (allowCustomObjectId && !el.id) {
2398+
throw new ParseError(
2399+
ParseError.MISSING_OBJECT_ID,
2400+
'objectId must not be empty, null or undefined'
2401+
);
2402+
}
23872403
pending.push(el);
23882404
}
23892405
});
@@ -2477,6 +2493,12 @@ const DefaultController = {
24772493
});
24782494
});
24792495
} else if (target instanceof ParseObject) {
2496+
if (allowCustomObjectId && !target.id) {
2497+
throw new ParseError(
2498+
ParseError.MISSING_OBJECT_ID,
2499+
'objectId must not be empty, null or undefined'
2500+
);
2501+
}
24802502
// generate _localId in case if cascadeSave=false
24812503
target._getId();
24822504
const localId = target._localId;

src/__tests__/Parse-test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,13 @@ describe('Parse module', () => {
161161
CoreManager.set('REQUEST_BATCH_SIZE', 20);
162162
});
163163

164+
it('can set allowCustomObjectId', () => {
165+
expect(Parse.allowCustomObjectId).toBe(false);
166+
Parse.allowCustomObjectId = true;
167+
expect(CoreManager.get('ALLOW_CUSTOM_OBJECT_ID')).toBe(true);
168+
Parse.allowCustomObjectId = false;
169+
});
170+
164171
it('_request', () => {
165172
const controller = {
166173
request: jest.fn(),

src/__tests__/ParseObject-test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3780,4 +3780,40 @@ describe('ParseObject pin', () => {
37803780
done();
37813781
});
37823782
});
3783+
3784+
it('can allowCustomObjectId', async done => {
3785+
CoreManager.set('ALLOW_CUSTOM_OBJECT_ID', true);
3786+
const o = new ParseObject('Person');
3787+
let params = o._getSaveParams();
3788+
expect(params).toEqual({
3789+
method: 'POST',
3790+
body: { objectId: undefined },
3791+
path: 'classes/Person',
3792+
});
3793+
try {
3794+
await o.save();
3795+
done.fail();
3796+
} catch (error) {
3797+
expect(error.message).toBe('objectId must not be empty, null or undefined');
3798+
}
3799+
try {
3800+
await ParseObject.saveAll([o]);
3801+
done.fail();
3802+
} catch (error) {
3803+
expect(error.message).toBe('objectId must not be empty, null or undefined');
3804+
}
3805+
o._finishFetch({
3806+
objectId: 'CUSTOM_ID',
3807+
createdAt: { __type: 'Date', iso: new Date().toISOString() },
3808+
updatedAt: { __type: 'Date', iso: new Date().toISOString() },
3809+
});
3810+
params = o._getSaveParams();
3811+
expect(params).toEqual({
3812+
method: 'PUT',
3813+
body: {},
3814+
path: 'classes/Person/CUSTOM_ID',
3815+
});
3816+
CoreManager.set('ALLOW_CUSTOM_OBJECT_ID', false);
3817+
done();
3818+
});
37833819
});

0 commit comments

Comments
 (0)