Skip to content
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

fix: Error when updating ParseObject with relation to another object #805

Closed
wants to merge 10 commits into from
15 changes: 11 additions & 4 deletions packages/dart/lib/src/objects/parse_object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -377,12 +377,19 @@ class ParseObject extends ParseBase implements ParseCloneable {
_arrayOperation('Add', key, <dynamic>[value]);
}

void addRelation(String key, List<dynamic> values) {
_arrayOperation('AddRelation', key, values);
void addRelation(String key, ParseRelation relation, List<dynamic> values) {
_getObjectData()[key] = relation;
Map<String, dynamic> change = {'__op': 'AddRelation', 'objects': values};
_unsavedChanges[key] =
ParseMergeTool().mergeWithPrevious(_unsavedChanges[key], change);
}

void removeRelation(String key, List<dynamic> values) {
_arrayOperation('RemoveRelation', key, values);
void removeRelation(
String key, ParseRelation relation, List<dynamic> values) {
_getObjectData()[key] = relation;
Map<String, dynamic> change = {'__op': 'RemoveRelation', 'objects': values};
_unsavedChanges[key] =
ParseMergeTool().mergeWithPrevious(_unsavedChanges[key], change);
}

/// Used in array Operations in save() method
Expand Down
51 changes: 34 additions & 17 deletions packages/dart/lib/src/objects/parse_relation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,64 @@ class ParseRelation<T extends ParseObject> {
_targetClass = parent.get<ParseRelation>(key)!.getTargetClass;
_parent = parent;
_key = key;
_parentObjectId = parent.objectId!;
}

ParseRelation.fromJson(Map<String, dynamic> map) {
_knownObjects = parseDecode(map['objects']);
_targetClass = map['className'];
}

//The owning object of this ParseRelation
ParseObject? _parent;
// The object Id of the parent.
String _parentObjectId = '';

//The className of the target objects.
String? _targetClass;
late final String _targetClass;

//The key of the relation in the parent object.
String _key = '';
late final String _key;

//For offline caching, we keep track of every object we've known to be in the relation.
Set<T>? _knownObjects = <T>{};
/// This cannot work as a cache!
/// Each time a relation instance is requested, a new instance is always returned.
/// It cannot be done otherwise if you want to work with custom objects.
/// This is due to the fact that when parsing the parent object
/// the first time you cannot dynamically create a ParseRelation<CustomObject extends ParseObject> instance.
/// Only a ParseRelation<ParseObject> can be created.
///
/// An alternative version is to avoid defining relations by relying on custom objects.
/// Then you can use this as a cache!
///
/// An important thing to note is that the server includes in the response the class name for the relation.
/// So, another valid proposal is to use the same pattern as used for custom objects
/// and use this class only as a default and allow the user to extend it.
/// In order to guarantee the correct type at runtime.
///
/// Set<T>? _knownObjects = <T>{};

QueryBuilder getQuery() {
return QueryBuilder(ParseCoreData.instance.createObject(_targetClass!))
..whereRelatedTo(_key, _parent!.parseClassName, _parentObjectId);
return QueryBuilder(ParseCoreData.instance.createObject(_targetClass))
..whereRelatedTo(_key, _parent!.parseClassName, _parent!.objectId!);
}

void add(T object) {
_targetClass = object.parseClassName;
_knownObjects!.add(object);
_parent!.addRelation(_key, _knownObjects!.toList());
_parent!.addRelation(_key, this, [object]);
}

void remove(T object) {
_targetClass = object.parseClassName;
_knownObjects!.remove(object);
_parent!.removeRelation(_key, _knownObjects!.toList());
_parent!.removeRelation(_key, this, [object]);
}

void addAll(List<T> object) {
_parent!.addRelation(_key, this, object);
}

void removeAll(List<T> object) {
_parent!.removeRelation(_key, this, object);
}

String get getTargetClass => _targetClass ?? '';
String get getTargetClass => _targetClass;

Map<String, dynamic> toJson() => <String, dynamic>{
'__type': keyRelation,
'className': _targetClass,
'objects': parseEncode(_knownObjects?.toList())
};
}
1 change: 1 addition & 0 deletions packages/dart/lib/src/utils/parse_decoder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ dynamic parseDecode(dynamic value) {
latitude: latitude.toDouble(), longitude: longitude.toDouble());
case 'Relation':
// ignore: always_specify_types
//TODO: bug with custom object relations
return ParseRelation.fromJson(map);
}
}
Expand Down
89 changes: 89 additions & 0 deletions packages/dart/test/relation_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import 'dart:convert';

import 'package:parse_server_sdk/parse_server_sdk.dart';
import 'package:test/test.dart';

void main() {
group('Relation', () {
setUp(() async {
await Parse().initialize(
juriSacchetta marked this conversation as resolved.
Show resolved Hide resolved
'appId', 'https://test.parse.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',
);
});

test('addRelation', () async {
var parentObj = {
"objectId": "mGGxAy3eek",
"relationKey": {"__type": "Relation", "className": "relationKey"}
};

Map<String, dynamic> map = json.decode(jsonEncode(parentObj));
final ParseObject parent = ParseObject.clone("ParentClass").fromJson(map);

final ParseObject child1 = ParseObject("ChildClass");
child1.objectId = "child1";
final ParseObject child2 = ParseObject("ChildClass");
child2.objectId = "child2";
ParseRelation parseRelation =
ParseRelation(parent: parent, key: "relationKey");
parent.addRelation("relationKey", parseRelation, [child1, child2]);

// desired output
var expectedResult = {
"relationKey": {
"__op": "AddRelation",
"objects": [
{
"__type": "Pointer",
"className": "ChildClass",
"objectId": "child1"
},
{
"__type": "Pointer",
"className": "ChildClass",
"objectId": "child2"
}
]
}
};

var act = parent.toJson(forApiRQ: true);
expect(act, expectedResult);
});
});

test('removeRelation', () async {
var parentObj = {
"objectId": "mGGxAy3eek",
"relationKey": {"__type": "Relation", "className": "relationKey"}
};

Map<String, dynamic> map = json.decode(jsonEncode(parentObj));
final ParseObject parent = ParseObject.clone("ParentClass").fromJson(map);
final ParseObject child1 = ParseObject("ChildClass");
child1.objectId = "child1";
parent.removeRelation("relationKey",
ParseRelation(parent: parent, key: "relationKey"), [child1]);

// desired output
var expectedResult = {
"relationKey": {
"__op": "RemoveRelation",
"objects": [
{"__type": "Pointer", "className": "ChildClass", "objectId": "child1"}
]
}
};
var act = parent.toJson(forApiRQ: true);
expect(act, expectedResult);
});
}