Skip to content

feat: Allow saving with custom objectId #1309

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions integration/test/ParseObjectTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -2045,4 +2045,60 @@ describe('Parse Object', () => {
expect(obj.get('string')).toBeDefined();
expect(obj.get('string')).toBeInstanceOf(String);
});

it('allowCustomObjectId', async () => {
await reconfigureServer({ allowCustomObjectId: true });
Parse.allowCustomObjectId = true;
const customId = `${Date.now()}`;
const object = new Parse.Object('TestObject');
try {
await object.save();
fail();
} catch (error) {
expect(error.message).toBe('objectId must not be empty, null or undefined');
}
object.id = customId;
object.set('foo', 'bar');
await object.save();
expect(object.id).toBe(customId);

const query = new Parse.Query('TestObject');
const result = await query.get(customId);
expect(result.get('foo')).toBe('bar');
expect(result.id).toBe(customId);

result.set('foo', 'baz');
await result.save();

const afterSave = await query.get(customId);
expect(afterSave.get('foo')).toBe('baz');
Parse.allowCustomObjectId = false;
});

it('allowCustomObjectId saveAll', async () => {
await reconfigureServer({ allowCustomObjectId: true });
Parse.allowCustomObjectId = true;
const customId1 = `${Date.now()}`;
const customId2 = `${Date.now()}`;
const obj1 = new TestObject({ foo: 'bar' });
const obj2 = new TestObject({ foo: 'baz' });
try {
await Parse.Object.saveAll([obj1, obj2]);
fail();
} catch (error) {
expect(error.message).toBe('objectId must not be empty, null or undefined');
}
obj1.id = customId1;
obj2.id = customId2;
await Parse.Object.saveAll([obj1, obj2]);
expect(obj1.id).toBe(customId1);
expect(obj2.id).toBe(customId2);

const query = new Parse.Query(TestObject);
const results = await query.find();
results.forEach(result => {
expect([customId1, customId2].includes(result.id));
});
Parse.allowCustomObjectId = false;
});
});
1 change: 1 addition & 0 deletions src/CoreManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ const config: Config & { [key: string]: mixed } = {
FORCE_REVOCABLE_SESSION: false,
ENCRYPTED_USER: false,
IDEMPOTENCY: false,
ALLOW_CUSTOM_OBJECT_ID: false,
};

function requireMethods(name: string, methods: Array<string>, controller: any) {
Expand Down
11 changes: 11 additions & 0 deletions src/Parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,17 @@ const Parse = {
get idempotency() {
return CoreManager.get('IDEMPOTENCY');
},

/**
* @member {boolean} Parse.allowCustomObjectId
* @static
*/
set allowCustomObjectId(value) {
CoreManager.set('ALLOW_CUSTOM_OBJECT_ID', value);
},
get allowCustomObjectId() {
return CoreManager.get('ALLOW_CUSTOM_OBJECT_ID');
},
};

Parse.ACL = require('./ParseACL').default;
Expand Down
25 changes: 23 additions & 2 deletions src/ParseObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,10 +324,18 @@ class ParseObject {
}

_getSaveParams(): SaveParams {
const method = this.id ? 'PUT' : 'POST';
let method = this.id ? 'PUT' : 'POST';
const body = this._getSaveJSON();
let path = 'classes/' + this.className;
if (this.id) {
if (CoreManager.get('ALLOW_CUSTOM_OBJECT_ID')) {
if (!this.createdAt) {
method = 'POST';
body.objectId = this.id;
} else {
method = 'PUT';
path += '/' + this.id;
}
} else if (this.id) {
path += '/' + this.id;
} else if (this.className === '_User') {
path = 'users';
Expand Down Expand Up @@ -2353,6 +2361,7 @@ const DefaultController = {

const RESTController = CoreManager.getRESTController();
const stateController = CoreManager.getObjectStateController();
const allowCustomObjectId = CoreManager.get('ALLOW_CUSTOM_OBJECT_ID');

options = options || {};
options.returnStatus = options.returnStatus || true;
Expand All @@ -2375,6 +2384,12 @@ const DefaultController = {
if (el instanceof ParseFile) {
filesSaved.push(el.save(options));
} else if (el instanceof ParseObject) {
if (allowCustomObjectId && !el.id) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This forces all objects to have custom ids, if custom ids are allowed.
Is this intentional? This is not how the server treats allowCustomObjectId.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I second this. Just turned this on, and this does not work as expected.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend looking at #1309 (comment)

throw new ParseError(
ParseError.MISSING_OBJECT_ID,
'objectId must not be empty, null or undefined'
);
}
pending.push(el);
}
});
Expand Down Expand Up @@ -2468,6 +2483,12 @@ const DefaultController = {
});
});
} else if (target instanceof ParseObject) {
if (allowCustomObjectId && !target.id) {
throw new ParseError(
ParseError.MISSING_OBJECT_ID,
'objectId must not be empty, null or undefined'
);
}
// generate _localId in case if cascadeSave=false
target._getId();
const localId = target._localId;
Expand Down
7 changes: 7 additions & 0 deletions src/__tests__/Parse-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ describe('Parse module', () => {
CoreManager.set('REQUEST_BATCH_SIZE', 20);
});

it('can set allowCustomObjectId', () => {
expect(Parse.allowCustomObjectId).toBe(false);
Parse.allowCustomObjectId = true;
expect(CoreManager.get('ALLOW_CUSTOM_OBJECT_ID')).toBe(true);
Parse.allowCustomObjectId = false;
});

it('getServerHealth', () => {
const controller = {
request: jest.fn(),
Expand Down
36 changes: 36 additions & 0 deletions src/__tests__/ParseObject-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3770,4 +3770,40 @@ describe('ParseObject pin', () => {
done();
});
});

it('can allowCustomObjectId', async done => {
CoreManager.set('ALLOW_CUSTOM_OBJECT_ID', true);
const o = new ParseObject('Person');
let params = o._getSaveParams();
expect(params).toEqual({
method: 'POST',
body: { objectId: undefined },
path: 'classes/Person',
});
try {
await o.save();
done.fail();
} catch (error) {
expect(error.message).toBe('objectId must not be empty, null or undefined');
}
try {
await ParseObject.saveAll([o]);
done.fail();
} catch (error) {
expect(error.message).toBe('objectId must not be empty, null or undefined');
}
o._finishFetch({
objectId: 'CUSTOM_ID',
createdAt: { __type: 'Date', iso: new Date().toISOString() },
updatedAt: { __type: 'Date', iso: new Date().toISOString() },
});
params = o._getSaveParams();
expect(params).toEqual({
method: 'PUT',
body: {},
path: 'classes/Person/CUSTOM_ID',
});
CoreManager.set('ALLOW_CUSTOM_OBJECT_ID', false);
done();
});
});