diff --git a/CHANGELOG.md b/CHANGELOG.md index 16a86c704..a26d549c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * Added `User.functions`. This is the entry point for calling Atlas App functions. Functions allow you to define and execute server-side logic for your application. Atlas App functions are created on the server, written in modern JavaScript (ES6+) and executed in a serverless manner. When you call a function, you can dynamically access components of the current application as well as information about the request to execute the function and the logged in user that sent the request. ([#973](https://github.com/realm/realm-dart/pull/973)) * Support results of primitives, ie. `RealmResult`. (Issue [#162](https://github.com/realm/realm-dart/issues/162)) * Support notifications on all managed realm lists, including list of primitives, ie. `RealmList.changes` is supported. ([#893](https://github.com/realm/realm-dart/pull/893)) -* Support named backlinks on realm models. You can now add and annotate a realm object iterator field with `@Backlink(#symbolName)`. ([#996](https://github.com/realm/realm-dart/pull/996)) +* Support named backlinks on realm models. You can now add and annotate a realm object iterator field with `@Backlink(#fieldName)`. ([#996](https://github.com/realm/realm-dart/pull/996)) ### Fixed * Fixed a wrong mapping for `AuthProviderType` returned by `User.provider` for google, facebook and apple credentials. diff --git a/common/lib/src/realm_common_base.dart b/common/lib/src/realm_common_base.dart index b2aa4700f..1db0dbf58 100644 --- a/common/lib/src/realm_common_base.dart +++ b/common/lib/src/realm_common_base.dart @@ -98,9 +98,10 @@ class Ignored { const Ignored(); } -/// Indicates a backlink property. +/// Indicates that the field it decorates is the inverse end of a relationship. /// {@category Annotations} class Backlink { - final Symbol symbol; - const Backlink(this.symbol); + /// The name of the field in the other class that links to this class. + final Symbol fieldName; + const Backlink(this.fieldName); } diff --git a/generator/lib/src/class_element_ex.dart b/generator/lib/src/class_element_ex.dart index 26fb301fa..9f69db4db 100644 --- a/generator/lib/src/class_element_ex.dart +++ b/generator/lib/src/class_element_ex.dart @@ -152,7 +152,7 @@ extension ClassElementEx on ClassElement { final objectType = ObjectType.values[modelInfo.value.getField('type')!.getField('index')!.toIntValue()!]; - // Computed fields go last. This is important for the schema generation. + // Realm Core requires computed properties at the end so we sort them at generation time versus doing it at runtime every time. final mappedFields = fields.realmInfo.toList()..sort((a, b) => a.isComputed ^ b.isComputed ? (a.isComputed ? 1 : -1) : -1); if (objectType == ObjectType.embeddedObject && mappedFields.any((field) => field.isPrimaryKey)) { diff --git a/generator/lib/src/dart_type_ex.dart b/generator/lib/src/dart_type_ex.dart index 05602b662..be1d34d6b 100644 --- a/generator/lib/src/dart_type_ex.dart +++ b/generator/lib/src/dart_type_ex.dart @@ -61,11 +61,11 @@ extension DartTypeEx on DartType { DartType get mappedType { final self = this; - final provider = session.typeProvider; if (isRealmCollection) { if (self is ParameterizedType) { final mapped = self.typeArguments.last.mappedType; if (self != mapped) { + final provider = session.typeProvider; if (self.isDartCoreList) { final mappedList = provider.listType(mapped); return PseudoType('Realm${mappedList.getDisplayString(withNullability: true)}', nullabilitySuffix: mappedList.nullabilitySuffix); diff --git a/generator/lib/src/field_element_ex.dart b/generator/lib/src/field_element_ex.dart index 8470b5135..e1ecf9f70 100644 --- a/generator/lib/src/field_element_ex.dart +++ b/generator/lib/src/field_element_ex.dart @@ -116,7 +116,7 @@ extension FieldElementEx on FieldElement { // // However, this may change in the future. Either as the dart language team change this // blemish. Or perhaps we can avoid the late modifier, once static meta programming lands - // in dart. Therefor we keep the code out-commented for later. + // in dart. Therefore we keep the code out-commented for later. /* if (!isFinal) { throw RealmInvalidGenerationSourceError( @@ -182,9 +182,9 @@ extension FieldElementEx on FieldElement { todo: todo, ); } else { - // Validate collections and back-links + // Validate collections and backlinks if (type.isRealmCollection || backlink != null) { - final typeDescription = type.isRealmCollection ? 'collections' : 'back-links'; + final typeDescription = type.isRealmCollection ? 'collections' : 'backlinks'; if (type.isNullable) { throw RealmInvalidGenerationSourceError( 'Realm $typeDescription cannot be nullable', @@ -204,7 +204,7 @@ extension FieldElementEx on FieldElement { } } - // Validate back-links + // Validate backlinks if (backlink != null) { if (!type.isDartCoreIterable || !(type as ParameterizedType).typeArguments.first.isRealmModel) { throw RealmInvalidGenerationSourceError( @@ -216,7 +216,7 @@ extension FieldElementEx on FieldElement { ); } - final sourceFieldName = backlink.value.getField('symbol')?.toSymbolValue(); + final sourceFieldName = backlink.value.getField('fieldName')?.toSymbolValue(); final sourceType = (type as ParameterizedType).typeArguments.first; final sourceField = (sourceType.element2 as ClassElement?)?.fields.where((f) => f.name == sourceFieldName).singleOrNull; @@ -235,7 +235,7 @@ extension FieldElementEx on FieldElement { final listOf = session.typeProvider.listType(thisType); if (sourceField.type != linkType && sourceField.type != listOf) { throw RealmInvalidGenerationSourceError( - 'Incompatible back-link type', + 'Incompatible backlink type', primarySpan: typeSpan(file), primaryLabel: "$sourceType.$sourceFieldName is not a '$linkType' or '$listOf'", todo: '', diff --git a/generator/test/error_test_data/backlink_illegal_symbol.expected b/generator/test/error_test_data/backlink_illegal_symbol.expected index f3dfd94c3..f2a819e91 100644 --- a/generator/test/error_test_data/backlink_illegal_symbol.expected +++ b/generator/test/error_test_data/backlink_illegal_symbol.expected @@ -1,4 +1,4 @@ -Incompatible back-link type +Incompatible backlink type in: asset:pkg/test/error_test_data/backlink_illegal_symbol.dart:11:8 ╷ diff --git a/generator/test/error_test_data/backlink_incompatible_type.expected b/generator/test/error_test_data/backlink_incompatible_type.expected index beea05d3f..2055fa453 100644 --- a/generator/test/error_test_data/backlink_incompatible_type.expected +++ b/generator/test/error_test_data/backlink_incompatible_type.expected @@ -1,4 +1,4 @@ -Incompatible back-link type +Incompatible backlink type in: asset:pkg/test/error_test_data/backlink_incompatible_type.dart:11:8 ╷ diff --git a/generator/test/test_util.dart b/generator/test/test_util.dart index cc1b1eaf3..d2621eeaf 100644 --- a/generator/test/test_util.dart +++ b/generator/test/test_util.dart @@ -65,7 +65,7 @@ class LinesEqualsMatcher extends Matcher { } } - if (actualLines.length > expectedLines.length) { + if (actualLines.length != expectedLines.length) { matchState["Error"] = "Different number of lines. \nExpected: ${expectedLines.length}\nActual: ${actualLines.length}"; return false; } diff --git a/lib/src/realm_object.dart b/lib/src/realm_object.dart index 797aab743..08da7ae00 100644 --- a/lib/src/realm_object.dart +++ b/lib/src/realm_object.dart @@ -149,26 +149,24 @@ class RealmCoreAccessor implements RealmAccessor { final sourceProperty = sourceMeta[propertyMeta.linkOriginProperty!]; final handle = realmCore.getBacklinks(object, sourceMeta.classKey, sourceProperty.key); return RealmResultsInternal.create(handle, object.realm, metadata); - } else { - final handle = realmCore.getListProperty(object, propertyMeta.key); - final listMetadata = propertyMeta.objectType == null ? null : object.realm.metadata.getByName(propertyMeta.objectType!); - - // listMetadata is not null when we have list of RealmObjects. If the API was - // called with a generic object arg - get we construct a list of - // RealmObjects since we don't know the type of the object. - if (listMetadata != null && _isTypeGenericObject()) { - switch (listMetadata.schema.baseType) { - case ObjectType.realmObject: - return object.realm.createList(handle, listMetadata); - case ObjectType.embeddedObject: - return object.realm.createList(handle, listMetadata); - default: - throw RealmError('List of ${listMetadata.schema.baseType} is not supported yet'); - } + } + final handle = realmCore.getListProperty(object, propertyMeta.key); + final listMetadata = propertyMeta.objectType == null ? null : object.realm.metadata.getByName(propertyMeta.objectType!); + + // listMetadata is not null when we have list of RealmObjects. If the API was + // called with a generic object arg - get we construct a list of + // RealmObjects since we don't know the type of the object. + if (listMetadata != null && _isTypeGenericObject()) { + switch (listMetadata.schema.baseType) { + case ObjectType.realmObject: + return object.realm.createList(handle, listMetadata); + case ObjectType.embeddedObject: + return object.realm.createList(handle, listMetadata); + default: + throw RealmError('List of ${listMetadata.schema.baseType} is not supported yet'); } - - return object.realm.createList(handle, listMetadata); } + return object.realm.createList(handle, listMetadata); } Object? value = realmCore.getProperty(object, propertyMeta.key); diff --git a/test/backlinks_test.dart b/test/backlinks_test.dart index 4f3481ffa..d0b31e477 100644 --- a/test/backlinks_test.dart +++ b/test/backlinks_test.dart @@ -35,12 +35,12 @@ class _Source { @RealmModel() class _Target { @Backlink(#oneTarget) - late Iterable<_Source> oneToMany; // computed property, so must go last in generated class! + late Iterable<_Source> oneToMany; String name = 'target'; @Backlink(#manyTargets) - late Iterable<_Source> manyToMany; // computed property, so must go last in generated class! + late Iterable<_Source> manyToMany; } Future main([List? args]) async {