diff --git a/README.md b/README.md index ab585d5e..9ab8b69a 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,35 @@ We can now change the `database` class like this: ``` You can also add individual tables or drop them. +### Extracting functionality with DAOs +When you have a lot of queries, putting them all into one class quickly becomes +tedious. You can avoid this by extracting some queries into classes that are +available from your main database class. Consider the following code: +```dart +part 'todos_dao.g.dart'; + +// the _TodosDaoMixin will be created by sally. It contains all the necessary +// fields for the tables. The type annotation is the database class +// that should use this dao. +@UseDao(tables: [Todos]) +class TodosDao extends DatabaseAccessor with _TodosDaoMixin { + // this constructor is required so that the main database can create an instance + // of this object. + TodosDao(Database db) : super(db); + + Stream> todosInCategory(Category category) { + if (category == null) { + return (select(todos)..where((t) => isNull(t.category))).watch(); + } else { + return (select(todos)..where((t) => t.category.equals(category.id))) + .watch(); + } + } +} +``` +If we now change the annotation on the `MyDatabase` class to `@UseSally(tables: [Todos, Categories], daos: [TodosDao])` +and re-run the code generation, a getter `todosDao` can be used to access the instance of that dao. + ## TODO-List and current limitations ### Limitations (at the moment) Please note that a workaround for most on this list exists with custom statements. @@ -266,11 +295,8 @@ These aren't sorted by priority. If you have more ideas or want some features ha let us know by creating an issue! - Specify primary keys - Support an simplified update that doesn't need an explicit where based on the primary key -- Data classes: Generate a `copyWith` method. - Simple `COUNT(*)` operations (group operations will be much more complicated) - Support default values and expressions -- Allow using DAOs or some other mechanism instead of having to put everything in the main -database class. - Support more Datatypes: We should at least support `Uint8List` out of the box, supporting floating / fixed point numbers as well would be awesome - Nullable / non-nullable datatypes diff --git a/sally_flutter/README.md b/sally_flutter/README.md index ab585d5e..9ab8b69a 100644 --- a/sally_flutter/README.md +++ b/sally_flutter/README.md @@ -253,6 +253,35 @@ We can now change the `database` class like this: ``` You can also add individual tables or drop them. +### Extracting functionality with DAOs +When you have a lot of queries, putting them all into one class quickly becomes +tedious. You can avoid this by extracting some queries into classes that are +available from your main database class. Consider the following code: +```dart +part 'todos_dao.g.dart'; + +// the _TodosDaoMixin will be created by sally. It contains all the necessary +// fields for the tables. The type annotation is the database class +// that should use this dao. +@UseDao(tables: [Todos]) +class TodosDao extends DatabaseAccessor with _TodosDaoMixin { + // this constructor is required so that the main database can create an instance + // of this object. + TodosDao(Database db) : super(db); + + Stream> todosInCategory(Category category) { + if (category == null) { + return (select(todos)..where((t) => isNull(t.category))).watch(); + } else { + return (select(todos)..where((t) => t.category.equals(category.id))) + .watch(); + } + } +} +``` +If we now change the annotation on the `MyDatabase` class to `@UseSally(tables: [Todos, Categories], daos: [TodosDao])` +and re-run the code generation, a getter `todosDao` can be used to access the instance of that dao. + ## TODO-List and current limitations ### Limitations (at the moment) Please note that a workaround for most on this list exists with custom statements. @@ -266,11 +295,8 @@ These aren't sorted by priority. If you have more ideas or want some features ha let us know by creating an issue! - Specify primary keys - Support an simplified update that doesn't need an explicit where based on the primary key -- Data classes: Generate a `copyWith` method. - Simple `COUNT(*)` operations (group operations will be much more complicated) - Support default values and expressions -- Allow using DAOs or some other mechanism instead of having to put everything in the main -database class. - Support more Datatypes: We should at least support `Uint8List` out of the box, supporting floating / fixed point numbers as well would be awesome - Nullable / non-nullable datatypes diff --git a/sally_flutter/example/README.md b/sally_flutter/example/README.md deleted file mode 100644 index 427b2d51..00000000 --- a/sally_flutter/example/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# sally_example - -This is a simple todo list app that uses sally as a persistence library. diff --git a/sally_flutter/example/lib/database/database.g.dart b/sally_flutter/example/lib/database/database.g.dart index 5ba6cadc..62e4d522 100644 --- a/sally_flutter/example/lib/database/database.g.dart +++ b/sally_flutter/example/lib/database/database.g.dart @@ -23,6 +23,14 @@ class TodoEntry { category: intType.mapFromDatabaseResponse(data['category']), ); } + TodoEntry copyWith( + {int id, String content, DateTime targetDate, int category}) => + TodoEntry( + id: id ?? this.id, + content: content ?? this.content, + targetDate: targetDate ?? this.targetDate, + category: category ?? this.category, + ); @override int get hashCode => (((id.hashCode) * 31 + content.hashCode) * 31 + targetDate.hashCode) * @@ -109,6 +117,10 @@ class Category { description: stringType.mapFromDatabaseResponse(data['`desc`']), ); } + Category copyWith({int id, String description}) => Category( + id: id ?? this.id, + description: description ?? this.description, + ); @override int get hashCode => (id.hashCode) * 31 + description.hashCode; @override diff --git a/sally_generator/lib/src/writer/data_class_writer.dart b/sally_generator/lib/src/writer/data_class_writer.dart index 292ceb0d..c74cbffb 100644 --- a/sally_generator/lib/src/writer/data_class_writer.dart +++ b/sally_generator/lib/src/writer/data_class_writer.dart @@ -26,6 +26,9 @@ class DataClassWriter { // Also write parsing factory _writeMappingConstructor(buffer); + // And a convenience method to copy data from this class. + _writeCopyWith(buffer); + buffer.write('@override\n int get hashCode => '); if (table.columns.isEmpty) { @@ -36,7 +39,7 @@ class DataClassWriter { } // override == - // return identical(this, other) || (other is DataClass && other.id == id && other.) + // return identical(this, other) || (other is DataClass && other.id == id && ...) buffer ..write('@override\nbool operator ==(other) => ') ..write('identical(this, other) || (other is ${table.dartTypeName}'); @@ -89,6 +92,32 @@ class DataClassWriter { buffer.write(');}\n'); } + void _writeCopyWith(StringBuffer buffer) { + final dataClassName = table.dartTypeName; + + buffer.write('$dataClassName copyWith({'); + for (var i = 0; i < table.columns.length; i++) { + final column = table.columns[i]; + final last = i == table.columns.length - 1; + + buffer.write('${column.dartTypeName} ${column.dartGetterName}'); + if (!last) { + buffer.write(','); + } + } + + buffer.write('}) => $dataClassName('); + + for (var column in table.columns) { + // we also have a method parameter called getter, so we can use + // field: field ?? this.field + final getter = column.dartGetterName; + buffer.write('$getter: $getter ?? this.$getter,'); + } + + buffer.write(');'); + } + /// Recursively creates the implementation for hashCode of the data class, /// assuming it has at least one field. When it has one field, we just return /// the hash code of that field. Otherwise, we multiply it with 31 and add