Allow named constructors in custom row classes

This commit is contained in:
Simon Binder 2021-05-09 12:42:02 +02:00
parent e05e8fe07e
commit 5f7c20d4eb
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
7 changed files with 53 additions and 17 deletions

View File

@ -144,9 +144,15 @@ class UseRowClass {
/// or types with arguments, are not allowed.
final Type type;
/// The name of the constructor to use.
///
/// When this option is not set, the default (unnamed) constructor will be
/// used to map database rows to the desired row class.
final String constructor;
/// Customize the class used by moor to hold an instance of an annotated
/// table.
///
/// For details, see the overall documentation on [UseRowClass].
const UseRowClass(this.type);
const UseRowClass(this.type, {this.constructor = ''});
}

View File

@ -58,7 +58,7 @@ class SharedTodos extends Table {
const _uuid = Uuid();
@UseRowClass(CustomRowClass)
@UseRowClass(CustomRowClass, constructor: 'map')
class TableWithoutPK extends Table {
IntColumn get notReallyAnId => integer()();
RealColumn get someFloat => real()();
@ -74,9 +74,13 @@ class CustomRowClass implements Insertable<CustomRowClass> {
final String? notFromDb;
CustomRowClass(this.notReallyAnId, double someFloat,
{required this.custom, this.notFromDb})
: anotherName = someFloat; // ignore: prefer_initializing_formals
CustomRowClass._(
this.notReallyAnId, this.anotherName, this.custom, this.notFromDb);
factory CustomRowClass.map(int notReallyAnId, double someFloat,
{required MyCustomObject custom, String? notFromDb}) {
return CustomRowClass._(notReallyAnId, someFloat, custom, notFromDb);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {

View File

@ -1205,7 +1205,7 @@ class $TableWithoutPKTable extends TableWithoutPK
@override
CustomRowClass map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null;
return CustomRowClass(
return CustomRowClass.map(
const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}not_really_an_id'])!,
const RealType()

View File

@ -30,9 +30,8 @@ void main() {
test('can insert floating point values', () async {
// regression test for https://github.com/simolus3/moor/issues/30
await db
.into(db.tableWithoutPK)
.insert(CustomRowClass(42, 3.1415, custom: MyCustomObject('custom')));
await db.into(db.tableWithoutPK).insert(
CustomRowClass.map(42, 3.1415, custom: MyCustomObject('custom')));
verify(executor.runInsert(
'INSERT INTO table_without_p_k '

View File

@ -4,14 +4,17 @@ import 'package:analyzer/dart/element/type.dart';
import 'package:moor_generator/moor_generator.dart';
import 'package:moor_generator/src/analyzer/errors.dart';
ExistingRowClass /*?*/ validateExistingClass(
List<MoorColumn> columns, ClassElement desiredClass, ErrorSink errors) {
final ctor = desiredClass.unnamedConstructor;
ExistingRowClass /*?*/ validateExistingClass(List<MoorColumn> columns,
ClassElement desiredClass, String constructor, ErrorSink errors) {
final ctor = desiredClass.getNamedConstructor(constructor);
if (ctor == null) {
errors.report(ErrorInDartCode(
affectedElement: desiredClass,
message: 'The desired data class must have an unnamed constructor',
));
final msg = constructor == ''
? 'The desired data class must have an unnamed constructor'
: 'The desired data class does not have a constructor named '
'$constructor';
errors.report(ErrorInDartCode(affectedElement: desiredClass, message: msg));
return null;
}

View File

@ -70,6 +70,7 @@ class TableParser {
String name;
ClassElement existingClass;
String constructorInExistingClass;
if (dataClassName != null) {
name = dataClassName.getField('name').toStringValue();
@ -79,6 +80,8 @@ class TableParser {
if (useRowClass != null) {
final type = useRowClass.getField('type').toTypeValue();
constructorInExistingClass =
useRowClass.getField('constructor').toStringValue();
if (type is InterfaceType) {
existingClass = type.element;
@ -93,7 +96,8 @@ class TableParser {
final verified = existingClass == null
? null
: validateExistingClass(columns, existingClass, base.step.errors);
: validateExistingClass(columns, existingClass,
constructorInExistingClass, base.step.errors);
return _DataClassInformation(name, verified);
}

View File

@ -17,6 +17,16 @@ class RowClass {
RowClass.create();
}
@UseRowClass(RowClass)
class TableClass extends Table {}
''',
'a|lib/invalid_no_named_constructor.dart': '''
import 'package:moor/moor.dart';
class RowClass {
RowClass();
RowClass.create();
}
@UseRowClass(RowClass, constructor: 'create2')
class TableClass extends Table {}
''',
'a|lib/mismatching_type.dart': '''
@ -79,6 +89,16 @@ class TableClass extends Table {
);
});
test('when no constructor with the right name exists', () async {
final file =
await state.analyze('package:a/invalid_no_named_constructor.dart');
expect(
file.errors.errors,
contains(isA<ErrorInDartCode>().having((e) => e.message, 'message',
contains('does not have a constructor named create2'))),
);
});
test('when a parameter has a mismatching type', () async {
final file = await state.analyze('package:a/mismatching_type.dart');
expect(