Merge pull request #1492 from westito/generate_tocompanion

Add 'toCompanion' generator for custom row classes
This commit is contained in:
Simon Binder 2021-10-20 23:39:18 +02:00 committed by GitHub
commit 38a8ad136f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 111 additions and 22 deletions

View File

@ -155,9 +155,17 @@ class UseRowClass {
/// used to map database rows to the desired row class.
final String constructor;
/// Generate a `toInsertable()` extension function for [type] mapping all
/// fields to an insertable object.
///
/// This can be useful when a custom data class should be used for inserts or
/// updates.
final bool generateInsertable;
/// Customize the class used by drift to hold an instance of an annotated
/// table.
///
/// For details, see the overall documentation on [UseRowClass].
const UseRowClass(this.type, {this.constructor = ''});
const UseRowClass(this.type,
{this.constructor = '', this.generateInsertable = false});
}

View File

@ -58,7 +58,7 @@ class SharedTodos extends Table {
const _uuid = Uuid();
@UseRowClass(CustomRowClass, constructor: 'map')
@UseRowClass(CustomRowClass, constructor: 'map', generateInsertable: true)
class TableWithoutPK extends Table {
IntColumn get notReallyAnId => integer()();
RealColumn get someFloat => real()();
@ -67,13 +67,15 @@ class TableWithoutPK extends Table {
text().map(const CustomConverter()).clientDefault(_uuid.v4)();
}
class CustomRowClass implements Insertable<CustomRowClass> {
class CustomRowClass {
final int notReallyAnId;
final double anotherName;
final MyCustomObject custom;
final String? notFromDb;
double get someFloat => anotherName;
CustomRowClass._(
this.notReallyAnId, this.anotherName, this.custom, this.notFromDb);
@ -81,15 +83,6 @@ class CustomRowClass implements Insertable<CustomRowClass> {
{required MyCustomObject custom, String? notFromDb}) {
return CustomRowClass._(notReallyAnId, someFloat, custom, notFromDb);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
return TableWithoutPKCompanion(
notReallyAnId: Value(notReallyAnId),
someFloat: Value(anotherName),
custom: Value(custom),
).toColumns(nullToAbsent);
}
}
class PureDefaults extends Table {

View File

@ -1054,6 +1054,27 @@ class TableWithoutPKCompanion extends UpdateCompanion<CustomRowClass> {
}
}
class _$CustomRowClassInsertable implements Insertable<CustomRowClass> {
CustomRowClass _object;
_$CustomRowClassInsertable(this._object);
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
return TableWithoutPKCompanion(
notReallyAnId: Value(_object.notReallyAnId),
someFloat: Value(_object.someFloat),
custom: Value(_object.custom),
).toColumns(false);
}
}
extension CustomRowClassToInsertable on CustomRowClass {
_$CustomRowClassInsertable toInsertable() {
return _$CustomRowClassInsertable(this);
}
}
class $TableWithoutPKTable extends TableWithoutPK
with TableInfo<$TableWithoutPKTable, CustomRowClass> {
final GeneratedDatabase _db;

View File

@ -31,7 +31,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.map(42, 3.1415, custom: MyCustomObject('custom')));
CustomRowClass.map(42, 3.1415, custom: MyCustomObject('custom'))
.toInsertable());
verify(executor.runInsert(
'INSERT INTO table_without_p_k '

View File

@ -15,8 +15,12 @@ class FoundDartClass {
FoundDartClass(this.classElement, this.instantiation);
}
ExistingRowClass? validateExistingClass(Iterable<MoorColumn> columns,
FoundDartClass dartClass, String constructor, ErrorSink errors) {
ExistingRowClass? validateExistingClass(
Iterable<MoorColumn> columns,
FoundDartClass dartClass,
String constructor,
bool generateInsertable,
ErrorSink errors) {
final desiredClass = dartClass.classElement;
ConstructorElement? ctor;
@ -52,8 +56,22 @@ ExistingRowClass? validateExistingClass(Iterable<MoorColumn> columns,
for (final parameter in ctor.parameters) {
final column = unmatchedColumnsByName.remove(parameter.name);
if (column != null) {
final matchField = !generateInsertable ||
dartClass.classElement.fields
.any((field) => field.name == parameter.name);
if (matchField) {
columnsToParameter[column] = parameter;
_checkType(parameter, column, errors);
} else {
final error = ErrorInDartCode(
affectedElement: parameter,
severity: Severity.criticalError,
message: 'Constructor parameter ${parameter.name} has no matching '
'field. When "generateInsertable" enabled, all constructor '
'parameter must have a matching field. Alternatively, you can '
'declare a getter field.');
throw Exception(error);
}
} else if (!parameter.isOptional) {
errors.report(ErrorInDartCode(
affectedElement: parameter,
@ -63,7 +81,8 @@ ExistingRowClass? validateExistingClass(Iterable<MoorColumn> columns,
}
}
return ExistingRowClass(desiredClass, ctor, columnsToParameter,
return ExistingRowClass(
desiredClass, ctor, columnsToParameter, generateInsertable,
typeInstantiation: dartClass.instantiation ?? const []);
}

View File

@ -70,6 +70,7 @@ class TableParser {
String name;
FoundDartClass? existingClass;
String? constructorInExistingClass;
bool? generateInsertable;
if (dataClassName != null) {
name = dataClassName.getField('name')!.toStringValue()!;
@ -81,6 +82,8 @@ class TableParser {
final type = useRowClass.getField('type')!.toTypeValue();
constructorInExistingClass =
useRowClass.getField('constructor')!.toStringValue()!;
generateInsertable =
useRowClass.getField('generateInsertable')!.toBoolValue()!;
if (type is InterfaceType) {
existingClass = FoundDartClass(type.element, type.typeArguments);
@ -96,7 +99,7 @@ class TableParser {
final verified = existingClass == null
? null
: validateExistingClass(columns, existingClass,
constructorInExistingClass!, base.step.errors);
constructorInExistingClass!, generateInsertable!, base.step.errors);
return _DataClassInformation(name, verified);
}

View File

@ -181,7 +181,7 @@ class CreateTableReader {
));
} else {
existingRowClass = validateExistingClass(
foundColumns.values, clazz, '', step.errors);
foundColumns.values, clazz, '', false, step.errors);
dataClassName = existingRowClass?.targetClass.name;
}
} else if (overriddenNames.contains('/')) {

View File

@ -64,7 +64,7 @@ class ViewAnalyzer extends BaseAnalyzer {
));
} else {
final rowClass = view.existingRowClass =
validateExistingClass(columns, clazz, '', step.errors);
validateExistingClass(columns, clazz, '', false, step.errors);
final newName = rowClass?.targetClass.name;
if (newName != null) {
view.dartTypeName = rowClass!.targetClass.name;

View File

@ -69,7 +69,11 @@ class ExistingRowClass {
final ConstructorElement constructor;
final Map<MoorColumn, ParameterElement> mapping;
ExistingRowClass(this.targetClass, this.constructor, this.mapping,
/// Generate toCompanion for data class
final bool generateInsertable;
ExistingRowClass(
this.targetClass, this.constructor, this.mapping, this.generateInsertable,
{this.typeInstantiation = const []});
String dartType([GenerationOptions options = const GenerationOptions()]) {

View File

@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:drift_dev/moor_generator.dart';
import 'package:drift_dev/src/utils/string_escaper.dart';
import 'package:drift_dev/src/writer/utils/override_toString.dart';
@ -28,6 +29,10 @@ class UpdateCompanionWriter {
_writeToString();
_buffer.write('}\n');
if (table.existingRowClass?.generateInsertable ?? false) {
_writeToCompanionExtension();
}
}
void _writeFields() {
@ -211,4 +216,39 @@ class UpdateCompanionWriter {
_buffer,
);
}
void _writeToCompanionExtension() {
final info = table.existingRowClass;
if (info == null) return;
final companionName = table.getNameForCompanionClass(scope.options);
final className = table.dartTypeName;
final insertableClass = '_\$${className}Insertable';
_buffer.write('class $insertableClass implements '
'Insertable<$className> {\n'
'$className _object;\n\n'
'$insertableClass(this._object);\n\n'
'@override\n'
'Map<String, Expression> toColumns(bool nullToAbsent) {\n'
'return $companionName(\n');
for (final field in info.mapping.values) {
final column =
table.columns.firstWhereOrNull((e) => e.dartGetterName == field.name);
if (column != null) {
final dartName = column.dartGetterName;
_buffer.write('$dartName: Value (_object.$dartName),\n');
}
}
_buffer
..write(').toColumns(false);\n}\n}\n\n')
..write('extension ${table.dartTypeName}ToInsertable '
'on ${table.dartTypeName} {')
..write('$insertableClass toInsertable() {\n')
..write('return _\$${className}Insertable(this);\n')
..write('}\n}\n');
}
}