Skip to content

Commit 1353997

Browse files
authored
Support Fetch With Include (#631)
* fetchWithInclude * improve coverage * Add fetchWithInclude to Users * improve docs * nit
1 parent ceb34c5 commit 1353997

File tree

7 files changed

+436
-2
lines changed

7 files changed

+436
-2
lines changed

integration/test/ParseObjectTest.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,6 +1340,93 @@ describe('Parse Object', () => {
13401340
});
13411341
});
13421342

1343+
it('can fetchWithInclude', async () => {
1344+
const parent = new TestObject();
1345+
const child = new TestObject();
1346+
child.set('field', 'isChild');
1347+
parent.set('child', child);
1348+
await parent.save();
1349+
1350+
const obj1 = TestObject.createWithoutData(parent.id);
1351+
const fetchedObj1 = await obj1.fetchWithInclude('child');
1352+
assert.equal(obj1.get('child').get('field'), 'isChild');
1353+
assert.equal(fetchedObj1.get('child').get('field'), 'isChild');
1354+
1355+
const obj2 = TestObject.createWithoutData(parent.id);
1356+
const fetchedObj2 = await obj2.fetchWithInclude(['child']);
1357+
assert.equal(obj2.get('child').get('field'), 'isChild');
1358+
assert.equal(fetchedObj2.get('child').get('field'), 'isChild');
1359+
1360+
const obj3 = TestObject.createWithoutData(parent.id);
1361+
const fetchedObj3 = await obj3.fetchWithInclude([ ['child'] ]);
1362+
assert.equal(obj3.get('child').get('field'), 'isChild');
1363+
assert.equal(fetchedObj3.get('child').get('field'), 'isChild');
1364+
});
1365+
1366+
it('can fetchWithInclude dot notation', async () => {
1367+
const parent = new TestObject();
1368+
const child = new TestObject();
1369+
const grandchild = new TestObject();
1370+
grandchild.set('field', 'isGrandchild');
1371+
child.set('grandchild', grandchild);
1372+
parent.set('child', child);
1373+
await Parse.Object.saveAll([parent, child, grandchild]);
1374+
1375+
const obj1 = TestObject.createWithoutData(parent.id);
1376+
await obj1.fetchWithInclude('child.grandchild');
1377+
assert.equal(obj1.get('child').get('grandchild').get('field'), 'isGrandchild');
1378+
1379+
const obj2 = TestObject.createWithoutData(parent.id);
1380+
await obj2.fetchWithInclude(['child.grandchild']);
1381+
assert.equal(obj2.get('child').get('grandchild').get('field'), 'isGrandchild');
1382+
1383+
const obj3 = TestObject.createWithoutData(parent.id);
1384+
await obj3.fetchWithInclude([ ['child.grandchild'] ]);
1385+
assert.equal(obj3.get('child').get('grandchild').get('field'), 'isGrandchild');
1386+
});
1387+
1388+
it('can fetchAllWithInclude', async () => {
1389+
const parent = new TestObject();
1390+
const child = new TestObject();
1391+
child.set('field', 'isChild');
1392+
parent.set('child', child);
1393+
await parent.save();
1394+
1395+
const obj1 = TestObject.createWithoutData(parent.id);
1396+
await Parse.Object.fetchAllWithInclude([obj1], 'child');
1397+
assert.equal(obj1.get('child').get('field'), 'isChild');
1398+
1399+
const obj2 = TestObject.createWithoutData(parent.id);
1400+
await Parse.Object.fetchAllWithInclude([obj2], ['child']);
1401+
assert.equal(obj2.get('child').get('field'), 'isChild');
1402+
1403+
const obj3 = TestObject.createWithoutData(parent.id);
1404+
await Parse.Object.fetchAllWithInclude([obj3], [ ['child'] ]);
1405+
assert.equal(obj3.get('child').get('field'), 'isChild');
1406+
});
1407+
1408+
it('can fetchAllWithInclude dot notation', async () => {
1409+
const parent = new TestObject();
1410+
const child = new TestObject();
1411+
const grandchild = new TestObject();
1412+
grandchild.set('field', 'isGrandchild');
1413+
child.set('grandchild', grandchild);
1414+
parent.set('child', child);
1415+
await Parse.Object.saveAll([parent, child, grandchild]);
1416+
1417+
const obj1 = TestObject.createWithoutData(parent.id);
1418+
await Parse.Object.fetchAllWithInclude([obj1], 'child.grandchild');
1419+
assert.equal(obj1.get('child').get('grandchild').get('field'), 'isGrandchild');
1420+
1421+
const obj2 = TestObject.createWithoutData(parent.id);
1422+
await Parse.Object.fetchAllWithInclude([obj2], ['child.grandchild']);
1423+
assert.equal(obj2.get('child').get('grandchild').get('field'), 'isGrandchild');
1424+
1425+
const obj3 = TestObject.createWithoutData(parent.id);
1426+
await Parse.Object.fetchAllWithInclude([obj3], [ ['child.grandchild'] ]);
1427+
assert.equal(obj3.get('child').get('grandchild').get('field'), 'isGrandchild');
1428+
});
1429+
13431430
it('fires errors when readonly attributes are changed', (done) => {
13441431
let LimitedObject = Parse.Object.extend('LimitedObject');
13451432
LimitedObject.readOnlyAttributes = function() {

integration/test/ParseUserTest.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,55 @@ describe('Parse User', () => {
193193
});
194194
});
195195

196+
it('can fetch non-auth user with include', async () => {
197+
Parse.User.enableUnsafeCurrentUser();
198+
199+
const child = new Parse.Object('TestObject');
200+
child.set('field', 'test');
201+
let user = new Parse.User();
202+
user.set('password', 'asdf');
203+
user.set('email', '[email protected]');
204+
user.set('username', 'zxcv');
205+
user.set('child', child);
206+
await user.signUp();
207+
208+
const query = new Parse.Query(Parse.User);
209+
const userNotAuthed = await query.get(user.id);
210+
211+
assert.equal(userNotAuthed.get('child').get('field'), undefined);
212+
213+
const fetchedUser = await userNotAuthed.fetchWithInclude('child');
214+
215+
assert.equal(userNotAuthed.get('child').get('field'), 'test');
216+
assert.equal(fetchedUser.get('child').get('field'), 'test');
217+
});
218+
219+
it('can fetch auth user with include', async () => {
220+
Parse.User.enableUnsafeCurrentUser();
221+
222+
const child = new Parse.Object('TestObject');
223+
child.set('field', 'test');
224+
let user = new Parse.User();
225+
user.set('password', 'asdf');
226+
user.set('email', '[email protected]');
227+
user.set('username', 'zxcv');
228+
user.set('child', child);
229+
await user.signUp();
230+
231+
user = await Parse.User.logIn('zxcv', 'asdf');
232+
233+
assert.equal(user.get('child').get('field'), undefined);
234+
assert.equal(Parse.User.current().get('child').get('field'), undefined);
235+
236+
const fetchedUser = await user.fetchWithInclude('child');
237+
const current = await Parse.User.currentAsync();
238+
239+
assert.equal(user.get('child').get('field'), 'test');
240+
assert.equal(current.get('child').get('field'), 'test');
241+
assert.equal(fetchedUser.get('child').get('field'), 'test');
242+
assert.equal(Parse.User.current().get('child').get('field'), 'test');
243+
});
244+
196245
it('can store the current user', (done) => {
197246
Parse.User.enableUnsafeCurrentUser();
198247
let user = new Parse.User();

src/ParseObject.js

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,8 @@ class ParseObject {
970970
* be used for this request.
971971
* <li>sessionToken: A valid session token, used for making a request on
972972
* behalf of a specific user.
973+
* <li>include: The name(s) of the key(s) to include. Can be a string, an array of strings,
974+
* or an array of array of strings.
973975
* </ul>
974976
* @return {Promise} A promise that is fulfilled when the fetch
975977
* completes.
@@ -983,10 +985,48 @@ class ParseObject {
983985
if (options.hasOwnProperty('sessionToken')) {
984986
fetchOptions.sessionToken = options.sessionToken;
985987
}
988+
if (options.hasOwnProperty('include')) {
989+
fetchOptions.include = [];
990+
if (Array.isArray(options.include)) {
991+
options.include.forEach((key) => {
992+
if (Array.isArray(key)) {
993+
fetchOptions.include = fetchOptions.include.concat(key);
994+
} else {
995+
fetchOptions.include.push(key);
996+
}
997+
});
998+
} else {
999+
fetchOptions.include.push(options.include);
1000+
}
1001+
}
9861002
var controller = CoreManager.getObjectController();
9871003
return controller.fetch(this, true, fetchOptions);
9881004
}
9891005

1006+
/**
1007+
* Fetch the model from the server. If the server's representation of the
1008+
* model differs from its current attributes, they will be overriden.
1009+
*
1010+
* Includes nested Parse.Objects for the provided key. You can use dot
1011+
* notation to specify which fields in the included object are also fetched.
1012+
*
1013+
* @param {String|Array<string|Array<string>>} keys The name(s) of the key(s) to include.
1014+
* @param {Object} options
1015+
* Valid options are:<ul>
1016+
* <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
1017+
* be used for this request.
1018+
* <li>sessionToken: A valid session token, used for making a request on
1019+
* behalf of a specific user.
1020+
* </ul>
1021+
* @return {Promise} A promise that is fulfilled when the fetch
1022+
* completes.
1023+
*/
1024+
fetchWithInclude(keys: String|Array<string|Array<string>>, options: RequestOptions): Promise {
1025+
options = options || {};
1026+
options.include = keys;
1027+
return this.fetch(options);
1028+
}
1029+
9901030
/**
9911031
* Set a hash of model attributes, and save the model to the server.
9921032
* updatedAt will be updated when the request returns.
@@ -1133,9 +1173,17 @@ class ParseObject {
11331173
*
11341174
* @param {Array} list A list of <code>Parse.Object</code>.
11351175
* @param {Object} options
1176+
* Valid options are:<ul>
1177+
* <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
1178+
* be used for this request.
1179+
* <li>sessionToken: A valid session token, used for making a request on
1180+
* behalf of a specific user.
1181+
* <li>include: The name(s) of the key(s) to include. Can be a string, an array of strings,
1182+
* or an array of array of strings.
1183+
* </ul>
11361184
* @static
11371185
*/
1138-
static fetchAll(list: Array<ParseObject>, options) {
1186+
static fetchAll(list: Array<ParseObject>, options: RequestOptions) {
11391187
var options = options || {};
11401188

11411189
var queryOptions = {};
@@ -1145,13 +1193,61 @@ class ParseObject {
11451193
if (options.hasOwnProperty('sessionToken')) {
11461194
queryOptions.sessionToken = options.sessionToken;
11471195
}
1196+
if (options.hasOwnProperty('include')) {
1197+
queryOptions.include = [];
1198+
if (Array.isArray(options.include)) {
1199+
options.include.forEach((key) => {
1200+
if (Array.isArray(key)) {
1201+
queryOptions.include = queryOptions.include.concat(key);
1202+
} else {
1203+
queryOptions.include.push(key);
1204+
}
1205+
});
1206+
} else {
1207+
queryOptions.include.push(options.include);
1208+
}
1209+
}
11481210
return CoreManager.getObjectController().fetch(
11491211
list,
11501212
true,
11511213
queryOptions
11521214
);
11531215
}
11541216

1217+
/**
1218+
* Fetches the given list of Parse.Object.
1219+
*
1220+
* Includes nested Parse.Objects for the provided key. You can use dot
1221+
* notation to specify which fields in the included object are also fetched.
1222+
*
1223+
* If any error is encountered, stops and calls the error handler.
1224+
*
1225+
* <pre>
1226+
* Parse.Object.fetchAllWithInclude([object1, object2, ...], [pointer1, pointer2, ...])
1227+
* .then((list) => {
1228+
* // All the objects were fetched.
1229+
* }, (error) => {
1230+
* // An error occurred while fetching one of the objects.
1231+
* });
1232+
* </pre>
1233+
*
1234+
* @param {Array} list A list of <code>Parse.Object</code>.
1235+
* @param {String|Array<string|Array<string>>} keys The name(s) of the key(s) to include.
1236+
* @param {Object} options
1237+
* Valid options are:<ul>
1238+
* <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
1239+
* be used for this request.
1240+
* <li>sessionToken: A valid session token, used for making a request on
1241+
* behalf of a specific user.
1242+
* </ul>
1243+
* @static
1244+
*/
1245+
static fetchAllWithInclude(list: Array<ParseObject>, keys: String|Array<string|Array<string>>, options: RequestOptions) {
1246+
options = options || {};
1247+
options.include = keys;
1248+
return ParseObject.fetchAll(list, options);
1249+
}
1250+
11551251
/**
11561252
* Fetches the given list of Parse.Object if needed.
11571253
* If any error is encountered, stops and calls the error handler.
@@ -1570,6 +1666,9 @@ var DefaultController = {
15701666
}
15711667
var query = new ParseQuery(className);
15721668
query.containedIn('objectId', ids);
1669+
if (options && options.include) {
1670+
query.include(options.include);
1671+
}
15731672
query._limit = ids.length;
15741673
return query.find(options).then((objects) => {
15751674
var idMap = {};
@@ -1604,10 +1703,14 @@ var DefaultController = {
16041703
});
16051704
} else {
16061705
var RESTController = CoreManager.getRESTController();
1706+
const params = {};
1707+
if (options && options.include) {
1708+
params.include = options.include.join();
1709+
}
16071710
return RESTController.request(
16081711
'GET',
16091712
'classes/' + target.className + '/' + target._getId(),
1610-
{},
1713+
params,
16111714
options
16121715
).then((response, status, xhr) => {
16131716
if (target instanceof ParseObject) {

src/ParseUser.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,19 @@ class ParseUser extends ParseObject {
463463
});
464464
}
465465

466+
/**
467+
* Wrap the default fetchWithInclude behavior with functionality to save to local
468+
* storage if this is current user.
469+
*/
470+
fetchWithInclude(...args: Array<any>): Promise {
471+
return super.fetchWithInclude.apply(this, args).then(() => {
472+
if (this.isCurrent()) {
473+
return CoreManager.getUserController().updateUserOnDisk(this);
474+
}
475+
return this;
476+
});
477+
}
478+
466479
static readOnlyAttributes() {
467480
return ['sessionToken'];
468481
}

src/RESTController.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type RequestOptions = {
1717
useMasterKey?: boolean;
1818
sessionToken?: string;
1919
installationId?: string;
20+
include?: any;
2021
};
2122

2223
export type FullOptions = {

0 commit comments

Comments
 (0)