Skip to content

feat: Add include option to getObject and fetch #798

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 13 commits into from
Dec 22, 2022
Merged
Show file tree
Hide file tree
Changes from 7 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
3 changes: 2 additions & 1 deletion packages/dart/lib/parse_server_sdk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';

import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import 'package:dio/dio.dart';
import 'package:meta/meta.dart';
import 'package:mime_type/mime_type.dart';
Expand Down
14 changes: 0 additions & 14 deletions packages/dart/lib/src/network/parse_query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -435,20 +435,6 @@ class QueryBuilder<T extends ParseObject> {
return queryBuilder;
}

String concatenateArray(List<String> queries) {
String queryBuilder = '';

for (final String item in queries) {
if (item == queries.first) {
queryBuilder += item;
} else {
queryBuilder += ',$item';
}
}

return queryBuilder;
}

/// Creates a query param using the column, the value and the queryOperator
/// that the column and value are being queried against
MapEntry<String, dynamic> _buildQueryWithColumnValueAndOperator(
Expand Down
1 change: 0 additions & 1 deletion packages/dart/lib/src/objects/parse_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ abstract class ParseBase {
}

/// Converts object to [String] in JSON format
@protected
Map<String, dynamic> toJson({
bool full = false,
bool forApiRQ = false,
Expand Down
20 changes: 20 additions & 0 deletions packages/dart/lib/src/objects/parse_installation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class ParseInstallation extends ParseObject {
set<String?>(keyLocaleIdentifier, ParseCoreData().locale);

//Timezone
set<String>(keyTimeZone, _getNameLocalTimeZone());

//App info
set<String?>(keyAppName, ParseCoreData().appName);
Expand All @@ -93,6 +94,25 @@ class ParseInstallation extends ParseObject {
set<String>(keyParseVersion, keySdkVersion);
}

String _getNameLocalTimeZone() {
tz.initializeTimeZones();
var locations = tz.timeZoneDatabase.locations;

int milliseconds = DateTime.now().timeZoneOffset.inMilliseconds;
String name = "";

locations.forEach((key, value) {
for (var element in value.zones) {
if (element.offset == milliseconds) {
name = value.name;
break;
}
}
});

return name;
}

@override
Future<ParseResponse> create({bool allowCustomObjectId = false}) async {
final bool isCurrent = await ParseInstallation.isCurrent(this);
Expand Down
16 changes: 12 additions & 4 deletions packages/dart/lib/src/objects/parse_object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,15 @@ class ParseObject extends ParseBase implements ParseCloneable {
late ParseClient _client;

/// Gets an object from the server using it's [String] objectId
Future<ParseResponse> getObject(String objectId) async {
///
/// `List<String>` include refers to other ParseObjects stored as a Pointer
Future<ParseResponse> getObject(String objectId,
{List<String>? include}) async {
try {
final String uri = '$_path/$objectId';
String uri = '$_path/$objectId';
if (include != null) {
uri = '$uri?include=${concatenateArray(include)}';
}
final Uri url = getSanitisedUri(_client, uri);

final ParseNetworkResponse result = await _client.get(url.toString());
Expand Down Expand Up @@ -451,12 +457,14 @@ class ParseObject extends ParseBase implements ParseCloneable {

///Fetches this object with the data from the server. Call this whenever you want the state of the
///object to reflect exactly what is on the server.
Future<ParseObject> fetch() async {
///
/// `List<String>` include refers to other ParseObjects stored as a Pointer
Future<ParseObject> fetch({List<String>? include}) async {
if (objectId == null || objectId!.isEmpty) {
throw 'can not fetch without a objectId';
}

final ParseResponse response = await getObject(objectId!);
final ParseResponse response = await getObject(objectId!, include: include);

if (response.success && response.results != null) {
return response.results!.first;
Expand Down
15 changes: 15 additions & 0 deletions packages/dart/lib/src/utils/parse_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@ bool isDebugEnabled({bool? objectLevelDebug}) {
return objectLevelDebug ?? ParseCoreData().debug;
}

/// Convert list of strings to a string with commas
String concatenateArray(List<String> list) {
String output = '';

for (final String item in list) {
if (item == list.first) {
output += item;
} else {
output += ',$item';
}
}

return output;
}

/// Converts the object to the correct value for JSON,
///
/// Strings are wrapped with "" but integers and others are not
Expand Down
1 change: 1 addition & 0 deletions packages/dart/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies:
meta: ^1.7.0
path: ^1.8.1
mime_type: ^1.0.0
timezone: ^0.9.0

dev_dependencies:
lints: ^1.0.1
Expand Down
2 changes: 1 addition & 1 deletion packages/dart/test/parse_encoder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ void main() {
// arrange
await Parse().initialize(
'appId',
'https://test.parse.com',
'https://example.com',
debug: true,
// to prevent automatic detection
fileDirectory: 'someDirectory',
Expand Down
130 changes: 130 additions & 0 deletions packages/dart/test/parse_object_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:parse_server_sdk/parse_server_sdk.dart';
import 'package:test/test.dart';
import 'parse_query_test.mocks.dart';

@GenerateMocks([ParseClient])
void main() {
group('parseObject', () {
test('should return expectedIncludeResult json when use fetch', () async {
// arrange
final MockParseClient client = MockParseClient();

await Parse().initialize(
'appId',
'https://example.com',
debug: true,
// to prevent automatic detection
fileDirectory: 'someDirectory',
// to prevent automatic detection
appName: 'appName',
// to prevent automatic detection
appPackageName: 'somePackageName',
// to prevent automatic detection
appVersion: 'someAppVersion',
);

ParseObject myUserObject = ParseObject("MyUser", client: client);
myUserObject.objectId = "Mn1iJTkWTE";

when(client.get(
any,
options: anyNamed("options"),
onReceiveProgress: anyNamed("onReceiveProgress"),
)).thenAnswer((_) async => ParseNetworkResponse(
statusCode: 200,
data:
"{\"results\":[{\"objectId\":\"Mn1iJTkWTE\",\"phone\":\"+12025550463\",\"createdAt\":\"2022-09-04T13:35:20.883Z\",\"updatedAt\":\"2022-11-14T10:55:56.202Z\",\"img\":{\"objectId\":\"8nGrLj3Mvk\",\"size\":67663,\"mime\":\"image/jpg\",\"file\":{\"__type\":\"File\",\"name\":\"dc7320ee9146ee19aed8997722fd4e3c.bin\",\"url\":\"http://ip:port/api/files/myapp/dc7320ee9146ee19aed8997722fd4e3c.bin\"},\"createdAt\":\"2022-11-14T10:55:56.025Z\",\"updatedAt\":\"2022-11-14T10:55:56.025Z\",\"__type\":\"Object\",\"className\":\"MyFile\"}}]}"));

// act
ParseObject parseObject = await myUserObject.fetch(include: ["img"]);

// desired output
String expectedIncludeResult =
'{className: MyFile, objectId: 8nGrLj3Mvk, createdAt: 2022-11-14T10:55:56.025Z, updatedAt: 2022-11-14T10:55:56.025Z, size: 67663, mime: image/jpg, file: {"__type":"File","name":"dc7320ee9146ee19aed8997722fd4e3c.bin","url":"http://ip:port/api/files/myapp/dc7320ee9146ee19aed8997722fd4e3c.bin"}}';

// asserts
expect(
parseEncode(parseObject.get<ParseObject>('img'), full: true)
.toString(),
expectedIncludeResult);
expect(parseObject['img'].objectId, "8nGrLj3Mvk");

// act
final Uri result = Uri.parse(verify(client.get(
captureAny,
options: anyNamed("options"),
onReceiveProgress: anyNamed("onReceiveProgress"),
)).captured.single);

// assert
expect(Uri.decodeComponent(result.path),
'/classes/MyUser/Mn1iJTkWTE?include=img');
});

test('should return expectedIncludeResult json when use getObject',
() async {
// arrange
final MockParseClient client = MockParseClient();

await Parse().initialize(
'appId',
'https://example.com',
debug: true,
// to prevent automatic detection
fileDirectory: 'someDirectory',
// to prevent automatic detection
appName: 'appName',
// to prevent automatic detection
appPackageName: 'somePackageName',
// to prevent automatic detection
appVersion: 'someAppVersion',
);

ParseObject myUserObject = ParseObject("MyUser", client: client);
myUserObject.objectId = "Mn1iJTkWTE";

when(client.get(
any,
options: anyNamed("options"),
onReceiveProgress: anyNamed("onReceiveProgress"),
)).thenAnswer((_) async => ParseNetworkResponse(
statusCode: 200,
data:
"{\"results\":[{\"objectId\":\"Mn1iJTkWTE\",\"phone\":\"+12025550463\",\"createdAt\":\"2022-09-04T13:35:20.883Z\",\"updatedAt\":\"2022-11-14T10:55:56.202Z\",\"img\":{\"objectId\":\"8nGrLj3Mvk\",\"size\":67663,\"mime\":\"image/jpg\",\"file\":{\"__type\":\"File\",\"name\":\"dc7320ee9146ee19aed8997722fd4e3c.bin\",\"url\":\"http://ip:port/api/files/myapp/dc7320ee9146ee19aed8997722fd4e3c.bin\"},\"createdAt\":\"2022-11-14T10:55:56.025Z\",\"updatedAt\":\"2022-11-14T10:55:56.025Z\",\"__type\":\"Object\",\"className\":\"MyFile\"}}]}"));

// act
ParseResponse response =
await myUserObject.getObject("Mn1iJTkWTE", include: ["img"]);

// asserts
expect(response.results?.first, isA<ParseObject>());

// get parseObject
ParseObject parseObject = response.results?.first;

// desired output
String expectedIncludeResult =
'{className: MyFile, objectId: 8nGrLj3Mvk, createdAt: 2022-11-14T10:55:56.025Z, updatedAt: 2022-11-14T10:55:56.025Z, size: 67663, mime: image/jpg, file: {"__type":"File","name":"dc7320ee9146ee19aed8997722fd4e3c.bin","url":"http://ip:port/api/files/myapp/dc7320ee9146ee19aed8997722fd4e3c.bin"}}';

// asserts
expect(
parseEncode(parseObject.get<ParseObject>('img'), full: true)
.toString(),
expectedIncludeResult);
expect(parseObject['img'].objectId, "8nGrLj3Mvk");

// act
final Uri result = Uri.parse(verify(client.get(
captureAny,
options: anyNamed("options"),
onReceiveProgress: anyNamed("onReceiveProgress"),
)).captured.single);

// assert
expect(Uri.decodeComponent(result.path),
'/classes/MyUser/Mn1iJTkWTE?include=img');
});
});
}
8 changes: 4 additions & 4 deletions packages/dart/test/parse_query_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ void main() {

await Parse().initialize(
'appId',
'https://test.parse.com',
'https://example.com',
debug: true,
// to prevent automatic detection
fileDirectory: 'someDirectory',
Expand Down Expand Up @@ -68,7 +68,7 @@ void main() {

await Parse().initialize(
'appId',
'https://test.parse.com',
'https://example.com',
debug: true,
// to prevent automatic detection
fileDirectory: 'someDirectory',
Expand Down Expand Up @@ -131,7 +131,7 @@ void main() {

await Parse().initialize(
'appId',
'https://test.parse.com',
'https://example.com',
debug: true,
// to prevent automatic detection
fileDirectory: 'someDirectory',
Expand Down Expand Up @@ -193,7 +193,7 @@ void main() {

await Parse().initialize(
'appId',
'https://test.parse.com',
'https://example.com',
debug: true,
// to prevent automatic detection
fileDirectory: 'someDirectory',
Expand Down