diff --git a/packages/dart/lib/src/objects/parse_object.dart b/packages/dart/lib/src/objects/parse_object.dart index 62f2ff566..a37511ebe 100644 --- a/packages/dart/lib/src/objects/parse_object.dart +++ b/packages/dart/lib/src/objects/parse_object.dart @@ -377,12 +377,19 @@ class ParseObject extends ParseBase implements ParseCloneable { _arrayOperation('Add', key, [value]); } - void addRelation(String key, List values) { - _arrayOperation('AddRelation', key, values); + void addRelation(String key, ParseRelation relation, List values) { + _getObjectData()[key] = relation; + Map change = {'__op': 'AddRelation', 'objects': values}; + _unsavedChanges[key] = + ParseMergeTool().mergeWithPrevious(_unsavedChanges[key], change); } - void removeRelation(String key, List values) { - _arrayOperation('RemoveRelation', key, values); + void removeRelation( + String key, ParseRelation relation, List values) { + _getObjectData()[key] = relation; + Map change = {'__op': 'RemoveRelation', 'objects': values}; + _unsavedChanges[key] = + ParseMergeTool().mergeWithPrevious(_unsavedChanges[key], change); } /// Used in array Operations in save() method diff --git a/packages/dart/lib/src/objects/parse_relation.dart b/packages/dart/lib/src/objects/parse_relation.dart index 678414821..37d1e87f1 100644 --- a/packages/dart/lib/src/objects/parse_relation.dart +++ b/packages/dart/lib/src/objects/parse_relation.dart @@ -9,47 +9,64 @@ class ParseRelation { _targetClass = parent.get(key)!.getTargetClass; _parent = parent; _key = key; - _parentObjectId = parent.objectId!; } ParseRelation.fromJson(Map 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? _knownObjects = {}; + /// 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 instance. + /// Only a ParseRelation 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? _knownObjects = {}; 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 object) { + _parent!.addRelation(_key, this, object); + } + + void removeAll(List object) { + _parent!.removeRelation(_key, this, object); } - String get getTargetClass => _targetClass ?? ''; + String get getTargetClass => _targetClass; Map toJson() => { '__type': keyRelation, 'className': _targetClass, - 'objects': parseEncode(_knownObjects?.toList()) }; } diff --git a/packages/dart/lib/src/utils/parse_decoder.dart b/packages/dart/lib/src/utils/parse_decoder.dart index 331f1173a..08866d6bd 100644 --- a/packages/dart/lib/src/utils/parse_decoder.dart +++ b/packages/dart/lib/src/utils/parse_decoder.dart @@ -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); } } diff --git a/packages/dart/test/relation_test.dart b/packages/dart/test/relation_test.dart new file mode 100644 index 000000000..841079ee6 --- /dev/null +++ b/packages/dart/test/relation_test.dart @@ -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( + '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 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 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); + }); +}