Skip to content

Commit 42caccc

Browse files
Generator: for @Backlink support specifying ToOne target ID property #160
To support backlinks to a ToOne with a renamed target ID property, also search for target ID ("relation") properties without adding the "Id" suffix. Also limit search to target ID ("relation") properties only. And improve the error message with details and suggestions.
1 parent 9e12073 commit 42caccc

File tree

2 files changed

+145
-2
lines changed

2 files changed

+145
-2
lines changed

generator/lib/src/code_builder.dart

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,11 +429,23 @@ class CodeBuilder extends Builder {
429429
srcRel = matchingRels.first;
430430
}
431431
} else {
432-
srcProp = srcEntity.findPropertyByName('${bl.srcField}Id');
432+
// For backwards compatibility, expect the name of the ToOne field, so add
433+
// an "Id" suffix to find the target ID property.
434+
srcProp = srcEntity.properties.firstWhereOrNull(
435+
(p) => p.isRelation && p.name == '${bl.srcField}Id',
436+
);
437+
// Otherwise, and to support ToOne with renamed target ID properties
438+
// (using @TargetIdProperty), expect the name of the target ID property.
439+
srcProp ??= srcEntity.properties.firstWhereOrNull(
440+
(p) => p.isRelation && p.name == bl.srcField,
441+
);
442+
// For ToMany, always expect the name of the ToMany field/relation
433443
srcRel = srcEntity.relations.firstWhereOrNull(
434444
(r) => r.name == bl.srcField,
435445
);
436446

447+
// This should be impossible as it would mean a ToOne and a ToMany field
448+
// share the same name, but check just in case.
437449
if (srcProp != null && srcRel != null) {
438450
throwAmbiguousError([srcProp], [srcRel]);
439451
}
@@ -445,7 +457,11 @@ class CodeBuilder extends Builder {
445457
return BacklinkSourceProperty(srcProp);
446458
} else {
447459
throw InvalidGenerationSourceError(
448-
"Unknown relation backlink source for '${entity.name}.${bl.name}'",
460+
'Failed to find backlink source for "${entity.name}.${bl.name}" in "${srcEntity.name}", '
461+
'make sure a matching ToOne or ToMany relation exists.'
462+
' If the ToOne target ID property is renamed with @TargetIdProperty,'
463+
' then make sure to specify the name of the target ID property instead of the ToOne,'
464+
" like @Backlink('<property>').",
449465
);
450466
}
451467
}

generator/test/code_builder_test.dart

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,94 @@ void main() {
367367
);
368368
});
369369

370+
test('Errors if backlink source does not exist', () async {
371+
final source = r'''
372+
library example;
373+
import 'package:objectbox/objectbox.dart';
374+
375+
@Entity()
376+
class Example {
377+
@Id()
378+
int id = 0;
379+
}
380+
381+
@Entity()
382+
class A {
383+
@Id()
384+
int id = 0;
385+
386+
@Backlink()
387+
final backRel = ToMany<Example>();
388+
}
389+
''';
390+
391+
final testEnv = GeneratorTestEnv();
392+
final result = await testEnv.run(source, ignoreOutput: true);
393+
394+
expect(result.builderResult.succeeded, false);
395+
expect(
396+
result.logs,
397+
contains(
398+
isA<LogRecord>()
399+
.having((r) => r.level, 'level', Level.SEVERE)
400+
.having(
401+
(r) => r.message,
402+
'message',
403+
contains(
404+
'Failed to find backlink source for "A.backRel" in "Example"',
405+
),
406+
),
407+
),
408+
);
409+
});
410+
411+
test(
412+
'Does not pick implicit backlink source if explicit one does not exist',
413+
() async {
414+
final source = r'''
415+
library example;
416+
import 'package:objectbox/objectbox.dart';
417+
418+
@Entity()
419+
class Example {
420+
@Id()
421+
int id = 0;
422+
423+
final relA1 = ToOne<A>();
424+
final relA2 = ToMany<A>();
425+
}
426+
427+
@Entity()
428+
class A {
429+
@Id()
430+
int id = 0;
431+
432+
@Backlink('doesnotexist')
433+
final backRel = ToMany<Example>();
434+
}
435+
''';
436+
437+
final testEnv = GeneratorTestEnv();
438+
final result = await testEnv.run(source, ignoreOutput: true);
439+
440+
expect(result.builderResult.succeeded, false);
441+
expect(
442+
result.logs,
443+
contains(
444+
isA<LogRecord>()
445+
.having((r) => r.level, 'level', Level.SEVERE)
446+
.having(
447+
(r) => r.message,
448+
'message',
449+
contains(
450+
'Failed to find backlink source for "A.backRel" in "Example"',
451+
),
452+
),
453+
),
454+
);
455+
},
456+
);
457+
370458
test('@TargetIdProperty ToOne annotation', () async {
371459
final source = r'''
372460
library example;
@@ -394,6 +482,45 @@ void main() {
394482
expect(renamedRelationProperty!.type, OBXPropertyType.Relation);
395483
});
396484

485+
test('Explicit backlink to renamed ToOne target ID property', () async {
486+
final source = r'''
487+
library example;
488+
import 'package:objectbox/objectbox.dart';
489+
490+
@Entity()
491+
class Example {
492+
@Id()
493+
int id = 0;
494+
495+
@TargetIdProperty('customerRef')
496+
final customer = ToOne<Customer>();
497+
}
498+
499+
@Entity()
500+
class Customer {
501+
@Id()
502+
int id = 0;
503+
504+
// Must specify the target ID property instead of the ToOne field
505+
@Backlink('customerRef')
506+
final backRel = ToMany<Example>();
507+
}
508+
''';
509+
510+
final testEnv = GeneratorTestEnv();
511+
await testEnv.run(source);
512+
513+
final customerEntity = testEnv.model.entities.firstWhere(
514+
(e) => e.name == 'Customer',
515+
);
516+
var backlinkSource = customerEntity.backlinks.first.source;
517+
expect(backlinkSource, isA<BacklinkSourceProperty>());
518+
expect(
519+
(backlinkSource as BacklinkSourceProperty).srcProp.relationField,
520+
'customer',
521+
);
522+
});
523+
397524
test('ToOne target ID property name conflict', () async {
398525
// Note: unlike in Java, for Dart it's also not supported to "expose" the
399526
// target ID (relation) property.

0 commit comments

Comments
 (0)