@hasOne Done
This commit is contained in:
parent
fa3ce3abc8
commit
62a4c15a2b
25 changed files with 850 additions and 93 deletions
6
.idea/runConfigurations/tests_in_has_one_test.xml
Normal file
6
.idea/runConfigurations/tests_in_has_one_test.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests in has_one_test" type="DartTestRunConfigurationType" factoryName="Dart Test" folderName="leg" singleton="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/angel_orm_generator/test/has_one_test.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -35,11 +35,17 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
|
|||
@override
|
||||
Future<String> generateForAnnotatedElement(
|
||||
Element element, ORM annotation, BuildStep buildStep) async {
|
||||
if (buildStep.inputId.path.contains('.orm.g.dart')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (element is! ClassElement)
|
||||
throw 'Only classes can be annotated with @serializable.';
|
||||
throw 'Only classes can be annotated with @ORM().';
|
||||
var resolver = await buildStep.resolver;
|
||||
return prettyToSource(
|
||||
generateOrmLibrary(element.library, resolver, buildStep).buildAst());
|
||||
var lib =
|
||||
generateOrmLibrary(element.library, resolver, buildStep).buildAst();
|
||||
if (lib == null) return null;
|
||||
return prettyToSource(lib);
|
||||
}
|
||||
|
||||
LibraryBuilder generateOrmLibrary(
|
||||
|
@ -91,6 +97,8 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
|
|||
}
|
||||
}
|
||||
|
||||
if (contexts.isEmpty) return null;
|
||||
|
||||
done.clear();
|
||||
for (var element in contexts.keys) {
|
||||
if (!done.contains(element.name)) {
|
||||
|
@ -252,12 +260,10 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
|
|||
ctx.relationships.forEach((name, r) {
|
||||
var relationship = ctx.populateRelationship(name);
|
||||
|
||||
// TODO: Has one, has many
|
||||
if (relationship.isBelongsTo) {
|
||||
var b = new StringBuffer(
|
||||
' INNER JOIN ${relationship.foreignTable} ON ${ctx.tableName}.${relationship.localKey} = ${relationship.foreignTable}.${relationship.foreignKey}');
|
||||
relationsIfThen
|
||||
.addStatement(buf.invoke('write', [literal(b.toString())]));
|
||||
// TODO: Belongs to many, has many
|
||||
if (relationship.isSingular) {
|
||||
String b = ' LEFT OUTER JOIN ${relationship.foreignTable} ON ${ctx.tableName}.${relationship.localKey} = ${relationship.foreignTable}.${relationship.foreignKey}';
|
||||
relationsIfThen.addStatement(buf.invoke('write', [literal(b)]));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -559,11 +565,8 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
|
|||
meth.addStatement(varField('whereClause',
|
||||
value: reference('where').invoke('toWhereClause', [])));
|
||||
|
||||
meth.addStatement(ifThen(whereClause.equals(literal(null)), [
|
||||
$buf.invoke('write', [literal('WHERE "id" = @id')]),
|
||||
elseThen([
|
||||
$buf.invoke('write', [whereClause])
|
||||
])
|
||||
meth.addStatement(ifThen(whereClause.notEquals(literal(null)), [
|
||||
$buf.invoke('write', [whereClause])
|
||||
]));
|
||||
|
||||
var buf2 = new StringBuffer();
|
||||
|
@ -690,38 +693,37 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
|
|||
void _applyRelationshipsToOutput(PostgresBuildContext ctx,
|
||||
ExpressionBuilder output, ExpressionBuilder row, MethodBuilder meth) {
|
||||
// Every relationship should fill itself in with a query
|
||||
// TODO: Has one, has many, belongs to many
|
||||
ctx.relationships.forEach((name, r) {
|
||||
var relationship = ctx.populateRelationship(name);
|
||||
|
||||
if (relationship.isBelongsTo) {
|
||||
var rc = new ReCase(relationship.isList
|
||||
? relationship.modelType.name
|
||||
: relationship.dartType.name);
|
||||
var type = new TypeBuilder('${rc.pascalCase}Query');
|
||||
var rc = new ReCase(relationship.isList
|
||||
? relationship.modelType.name
|
||||
: relationship.dartType.name);
|
||||
var type = new TypeBuilder('${rc.pascalCase}Query');
|
||||
|
||||
// Resolve index within row...
|
||||
bool matched = false;
|
||||
int col = 0;
|
||||
for (var field in ctx.fields) {
|
||||
if (field is RelationshipConstraintField &&
|
||||
field.originalName == name) {
|
||||
matched = true;
|
||||
break;
|
||||
} else
|
||||
col++;
|
||||
}
|
||||
// Resolve index within row...
|
||||
bool matched = false;
|
||||
int col = 0;
|
||||
for (var field in ctx.fields) {
|
||||
if (field is RelationshipConstraintField &&
|
||||
field.originalName == name) {
|
||||
matched = true;
|
||||
break;
|
||||
} else
|
||||
col++;
|
||||
}
|
||||
|
||||
if (!matched) {
|
||||
matched = ctx.resolveRelationshipField(name) != null;
|
||||
}
|
||||
if (!matched) {
|
||||
matched = ctx.resolveRelationshipField(name) != null;
|
||||
}
|
||||
|
||||
if (!matched)
|
||||
throw 'Couldn\'t resolve row index for relationship "${name}".';
|
||||
if (!matched)
|
||||
throw 'Couldn\'t resolve row index for relationship "${name}".';
|
||||
|
||||
var idAsInt = row[literal(col)];
|
||||
var idAsInt = row[literal(col)];
|
||||
|
||||
if (relationship.isSingular) {
|
||||
if (relationship.isSingular) {
|
||||
if (relationship.isBelongsTo) {
|
||||
meth.addStatement(type
|
||||
.invoke('getOne', [idAsInt, reference('connection')])
|
||||
.asAwait()
|
||||
|
@ -730,20 +732,38 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
|
|||
var query = reference('${rc.camelCase}Query');
|
||||
meth.addStatement(
|
||||
varField('${rc.camelCase}Query', value: type.newInstance([])));
|
||||
ExpressionBuilder fetched;
|
||||
|
||||
// TODO: HasMany
|
||||
if (relationship.isBelongsTo) {
|
||||
meth.addStatement(query
|
||||
.property('where')
|
||||
.property('id')
|
||||
.invoke('equals', [idAsInt]));
|
||||
fetched = query.invoke('get', [reference('connection')]).invoke(
|
||||
'toList', []).asAwait();
|
||||
}
|
||||
|
||||
meth.addStatement(output.property(name).invoke('addAll', [fetched]));
|
||||
// Set id to row[0]
|
||||
meth.addStatement(query
|
||||
.property('where')
|
||||
.property('id')
|
||||
.invoke('equals', [row[literal(0)]]));
|
||||
var fetched = query
|
||||
.invoke('get', [reference('connection')])
|
||||
.property('first')
|
||||
.invoke('catchError', [
|
||||
new MethodBuilder.closure(returns: literal(null))
|
||||
..addPositional(parameter('_'))
|
||||
])
|
||||
.asAwait();
|
||||
meth.addStatement(fetched.asAssign(output.property(name)));
|
||||
}
|
||||
} else {
|
||||
var query = reference('${rc.camelCase}Query');
|
||||
meth.addStatement(
|
||||
varField('${rc.camelCase}Query', value: type.newInstance([])));
|
||||
ExpressionBuilder fetched;
|
||||
|
||||
// TODO: HasMany
|
||||
if (relationship.isBelongsTo) {
|
||||
meth.addStatement(query
|
||||
.property('where')
|
||||
.property('id')
|
||||
.invoke('equals', [idAsInt]));
|
||||
fetched = query.invoke('get', [reference('connection')]).invoke(
|
||||
'toList', []).asAwait();
|
||||
}
|
||||
|
||||
meth.addStatement(output.property(name).invoke('addAll', [fetched]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -109,11 +109,12 @@ class PostgresBuildContext extends BuildContext {
|
|||
|
||||
if (relationship.type == RelationshipType.HAS_ONE ||
|
||||
relationship.type == RelationshipType.HAS_MANY) {
|
||||
var foreignKey = relationship.localKey ??
|
||||
var single = singularize(tableName);
|
||||
var foreignKey = relationship.foreignTable ??
|
||||
(autoSnakeCaseNames != false
|
||||
? '${rc.snakeCase}_id'
|
||||
: '${typeName}Id');
|
||||
var localKey = relationship.foreignKey ?? 'id';
|
||||
? '${single}_id'
|
||||
: '${single}Id');
|
||||
var localKey = relationship.localKey ?? 'id';
|
||||
var foreignTable = relationship.foreignTable ??
|
||||
(autoSnakeCaseNames != false
|
||||
? pluralize(rc.snakeCase)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: angel_orm_generator
|
||||
version: 1.0.0-alpha+2
|
||||
version: 1.0.0-alpha+3
|
||||
description: Code generators for Angel's ORM.
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/orm
|
||||
|
|
|
@ -12,9 +12,9 @@ main() {
|
|||
User john;
|
||||
|
||||
setUp(() async {
|
||||
connection = await connectToPostgres();
|
||||
connection = await connectToPostgres(['user', 'role']);
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
tearDown(() => connection.close());
|
||||
|
|
|
@ -15,7 +15,7 @@ main() {
|
|||
Book deathlyHallows;
|
||||
|
||||
setUp(() async {
|
||||
connection = await connectToPostgres();
|
||||
connection = await connectToPostgres(['author', 'book']);
|
||||
|
||||
// Insert an author
|
||||
rowling = await AuthorQuery.insert(connection, name: 'J.K. Rowling');
|
||||
|
|
|
@ -2,22 +2,15 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
import 'package:postgres/postgres.dart';
|
||||
|
||||
Future<PostgreSQLConnection> connectToPostgres() async {
|
||||
Future<PostgreSQLConnection> connectToPostgres(Iterable<String> schemas) async {
|
||||
var conn = new PostgreSQLConnection('127.0.0.1', 5432, 'angel_orm_test',
|
||||
username: Platform.environment['POSTGRES_USERNAME'] ?? 'postgres',
|
||||
password: Platform.environment['POSTGRES_PASSWORD'] ?? 'password');
|
||||
await conn.open();
|
||||
|
||||
var query = await new File('test/models/car.up.g.sql').readAsString();
|
||||
await conn.execute(query);
|
||||
query = await new File('test/models/author.up.g.sql').readAsString();
|
||||
await conn.execute(query);
|
||||
query = await new File('test/models/book.up.g.sql').readAsString();
|
||||
await conn.execute(query);
|
||||
query = await new File('test/models/role.up.g.sql').readAsString();
|
||||
await conn.execute(query);
|
||||
query = await new File('test/models/user.up.g.sql').readAsString();
|
||||
await conn.execute(query);
|
||||
for (var s in schemas)
|
||||
await conn
|
||||
.execute(await new File('test/models/$s.up.g.sql').readAsString());
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
|
73
angel_orm_generator/test/has_one_test.dart
Normal file
73
angel_orm_generator/test/has_one_test.dart
Normal file
|
@ -0,0 +1,73 @@
|
|||
/// Tests for @hasOne...
|
||||
library angel_orm_generator.test.has_one_test;
|
||||
|
||||
import 'package:postgres/postgres.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'models/foot.orm.g.dart';
|
||||
import 'models/leg.dart';
|
||||
import 'models/leg.orm.g.dart';
|
||||
import 'common.dart';
|
||||
|
||||
main() {
|
||||
PostgreSQLConnection connection;
|
||||
Leg originalLeg;
|
||||
|
||||
setUp(() async {
|
||||
connection = await connectToPostgres(['leg', 'foot']);
|
||||
originalLeg = await LegQuery.insert(connection, name: 'Left');
|
||||
});
|
||||
|
||||
test('sets to null if no child', () async {
|
||||
var leg = await LegQuery.getOne(int.parse(originalLeg.id), connection);
|
||||
expect(leg.name, originalLeg.name);
|
||||
expect(leg.id, originalLeg.id);
|
||||
expect(leg.foot, isNull);
|
||||
});
|
||||
|
||||
test('can fetch one foot', () async {
|
||||
var foot = await FootQuery.insert(connection,
|
||||
legId: int.parse(originalLeg.id), nToes: 5);
|
||||
var leg = await LegQuery.getOne(int.parse(originalLeg.id), connection);
|
||||
expect(leg.name, originalLeg.name);
|
||||
expect(leg.id, originalLeg.id);
|
||||
expect(leg.foot, isNotNull);
|
||||
expect(leg.foot.id, foot.id);
|
||||
expect(leg.foot.nToes, foot.nToes);
|
||||
});
|
||||
|
||||
test('only fetches one foot even if there are multiple', () async {
|
||||
var foot = await FootQuery.insert(connection,
|
||||
legId: int.parse(originalLeg.id), nToes: 5);
|
||||
await FootQuery.insert(connection,
|
||||
legId: int.parse(originalLeg.id), nToes: 24);
|
||||
var leg = await LegQuery.getOne(int.parse(originalLeg.id), connection);
|
||||
expect(leg.name, originalLeg.name);
|
||||
expect(leg.id, originalLeg.id);
|
||||
expect(leg.foot, isNotNull);
|
||||
expect(leg.foot.id, foot.id);
|
||||
expect(leg.foot.nToes, foot.nToes);
|
||||
});
|
||||
|
||||
test('sets foot on update', () async {
|
||||
var foot = await FootQuery.insert(connection,
|
||||
legId: int.parse(originalLeg.id), nToes: 5);
|
||||
var leg = await LegQuery.updateLeg(
|
||||
connection, originalLeg.clone()..name = 'Right');
|
||||
print(leg.toJson());
|
||||
expect(leg.name, 'Right');
|
||||
expect(leg.foot, isNotNull);
|
||||
expect(leg.foot.id, foot.id);
|
||||
expect(leg.foot.nToes, foot.nToes);
|
||||
});
|
||||
|
||||
test('sets foot on delete', () async {
|
||||
var foot = await FootQuery.insert(connection,
|
||||
legId: int.parse(originalLeg.id), nToes: 5);
|
||||
var leg = await LegQuery.deleteOne(int.parse(originalLeg.id), connection);
|
||||
print(leg.toJson());
|
||||
expect(leg.name, originalLeg.name);
|
||||
expect(leg.foot, isNotNull);
|
||||
expect(leg.foot.id, foot.id);
|
||||
expect(leg.foot.nToes, foot.nToes);
|
||||
});
|
||||
}
|
|
@ -125,9 +125,7 @@ class AuthorQuery {
|
|||
var buf = new StringBuffer(
|
||||
'UPDATE "authors" SET ("name", "created_at", "updated_at") = (@name, @createdAt, @updatedAt) ');
|
||||
var whereClause = where.toWhereClause();
|
||||
if (whereClause == null) {
|
||||
buf.write('WHERE "id" = @id');
|
||||
} else {
|
||||
if (whereClause != null) {
|
||||
buf.write(whereClause);
|
||||
}
|
||||
var __ormNow__ = new DateTime.now();
|
||||
|
|
|
@ -54,7 +54,7 @@ class BookQuery {
|
|||
? prefix
|
||||
: 'SELECT books.id, books.name, books.created_at, books.updated_at, books.author_id, authors.id, authors.name, authors.created_at, authors.updated_at FROM "books"');
|
||||
if (prefix == null) {
|
||||
buf.write(' INNER JOIN authors ON books.author_id = authors.id');
|
||||
buf.write(' LEFT OUTER JOIN authors ON books.author_id = authors.id');
|
||||
}
|
||||
var whereClause = where.toWhereClause();
|
||||
if (whereClause != null) {
|
||||
|
@ -133,9 +133,7 @@ class BookQuery {
|
|||
var buf = new StringBuffer(
|
||||
'UPDATE "books" SET ("name", "created_at", "updated_at", "author_id") = (@name, @createdAt, @updatedAt, @authorId) ');
|
||||
var whereClause = where.toWhereClause();
|
||||
if (whereClause == null) {
|
||||
buf.write('WHERE "id" = @id');
|
||||
} else {
|
||||
if (whereClause != null) {
|
||||
buf.write(whereClause);
|
||||
}
|
||||
var __ormNow__ = new DateTime.now();
|
||||
|
|
|
@ -133,9 +133,7 @@ class CarQuery {
|
|||
var buf = new StringBuffer(
|
||||
'UPDATE "cars" SET ("make", "description", "family_friendly", "recalled_at", "created_at", "updated_at") = (@make, @description, @familyFriendly, @recalledAt, @createdAt, @updatedAt) ');
|
||||
var whereClause = where.toWhereClause();
|
||||
if (whereClause == null) {
|
||||
buf.write('WHERE "id" = @id');
|
||||
} else {
|
||||
if (whereClause != null) {
|
||||
buf.write(whereClause);
|
||||
}
|
||||
var __ormNow__ = new DateTime.now();
|
||||
|
|
12
angel_orm_generator/test/models/foot.dart
Normal file
12
angel_orm_generator/test/models/foot.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
library angel_orm_generator.test.models.foot;
|
||||
|
||||
import 'package:angel_model/angel_model.dart';
|
||||
import 'package:angel_orm/angel_orm.dart';
|
||||
import 'package:angel_serialize/angel_serialize.dart';
|
||||
part 'foot.g.dart';
|
||||
|
||||
@serializable
|
||||
@orm
|
||||
class _Foot extends Model {
|
||||
int legId, nToes;
|
||||
}
|
1
angel_orm_generator/test/models/foot.down.g.sql
Normal file
1
angel_orm_generator/test/models/foot.down.g.sql
Normal file
|
@ -0,0 +1 @@
|
|||
DROP TABLE "foots";
|
58
angel_orm_generator/test/models/foot.g.dart
Normal file
58
angel_orm_generator/test/models/foot.g.dart
Normal file
|
@ -0,0 +1,58 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of angel_orm_generator.test.models.foot;
|
||||
|
||||
// **************************************************************************
|
||||
// Generator: JsonModelGenerator
|
||||
// Target: class _Foot
|
||||
// **************************************************************************
|
||||
|
||||
class Foot extends _Foot {
|
||||
@override
|
||||
String id;
|
||||
|
||||
@override
|
||||
int legId;
|
||||
|
||||
@override
|
||||
int nToes;
|
||||
|
||||
@override
|
||||
DateTime createdAt;
|
||||
|
||||
@override
|
||||
DateTime updatedAt;
|
||||
|
||||
Foot({this.id, this.legId, this.nToes, this.createdAt, this.updatedAt});
|
||||
|
||||
factory Foot.fromJson(Map data) {
|
||||
return new Foot(
|
||||
id: data['id'],
|
||||
legId: data['leg_id'],
|
||||
nToes: data['n_toes'],
|
||||
createdAt: data['created_at'] is DateTime
|
||||
? data['created_at']
|
||||
: (data['created_at'] is String
|
||||
? DateTime.parse(data['created_at'])
|
||||
: null),
|
||||
updatedAt: data['updated_at'] is DateTime
|
||||
? data['updated_at']
|
||||
: (data['updated_at'] is String
|
||||
? DateTime.parse(data['updated_at'])
|
||||
: null));
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'leg_id': legId,
|
||||
'n_toes': nToes,
|
||||
'created_at': createdAt == null ? null : createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt == null ? null : updatedAt.toIso8601String()
|
||||
};
|
||||
|
||||
static Foot parse(Map map) => new Foot.fromJson(map);
|
||||
|
||||
Foot clone() {
|
||||
return new Foot.fromJson(toJson());
|
||||
}
|
||||
}
|
253
angel_orm_generator/test/models/foot.orm.g.dart
Normal file
253
angel_orm_generator/test/models/foot.orm.g.dart
Normal file
|
@ -0,0 +1,253 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
// **************************************************************************
|
||||
// Generator: PostgresORMGenerator
|
||||
// Target: class _Foot
|
||||
// **************************************************************************
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angel_orm/angel_orm.dart';
|
||||
import 'package:postgres/postgres.dart';
|
||||
import 'foot.dart';
|
||||
|
||||
class FootQuery {
|
||||
final Map<FootQuery, bool> _unions = {};
|
||||
|
||||
String _sortKey;
|
||||
|
||||
String _sortMode;
|
||||
|
||||
int limit;
|
||||
|
||||
int offset;
|
||||
|
||||
final List<FootQueryWhere> _or = [];
|
||||
|
||||
final FootQueryWhere where = new FootQueryWhere();
|
||||
|
||||
void union(FootQuery query) {
|
||||
_unions[query] = false;
|
||||
}
|
||||
|
||||
void unionAll(FootQuery query) {
|
||||
_unions[query] = true;
|
||||
}
|
||||
|
||||
void sortDescending(String key) {
|
||||
_sortMode = 'Descending';
|
||||
_sortKey = ('' + key);
|
||||
}
|
||||
|
||||
void sortAscending(String key) {
|
||||
_sortMode = 'Ascending';
|
||||
_sortKey = ('' + key);
|
||||
}
|
||||
|
||||
void or(FootQueryWhere selector) {
|
||||
_or.add(selector);
|
||||
}
|
||||
|
||||
String toSql([String prefix]) {
|
||||
var buf = new StringBuffer();
|
||||
buf.write(prefix != null
|
||||
? prefix
|
||||
: 'SELECT id, leg_id, n_toes, created_at, updated_at FROM "foots"');
|
||||
if (prefix == null) {}
|
||||
var whereClause = where.toWhereClause();
|
||||
if (whereClause != null) {
|
||||
buf.write(' ' + whereClause);
|
||||
}
|
||||
_or.forEach((x) {
|
||||
var whereClause = x.toWhereClause(keyword: false);
|
||||
if (whereClause != null) {
|
||||
buf.write(' OR (' + whereClause + ')');
|
||||
}
|
||||
});
|
||||
if (prefix == null) {
|
||||
if (limit != null) {
|
||||
buf.write(' LIMIT ' + limit.toString());
|
||||
}
|
||||
if (offset != null) {
|
||||
buf.write(' OFFSET ' + offset.toString());
|
||||
}
|
||||
if (_sortMode == 'Descending') {
|
||||
buf.write(' ORDER BY "' + _sortKey + '" DESC');
|
||||
}
|
||||
if (_sortMode == 'Ascending') {
|
||||
buf.write(' ORDER BY "' + _sortKey + '" ASC');
|
||||
}
|
||||
_unions.forEach((query, all) {
|
||||
buf.write(' UNION');
|
||||
if (all) {
|
||||
buf.write(' ALL');
|
||||
}
|
||||
buf.write(' (');
|
||||
var sql = query.toSql().replaceAll(';', '');
|
||||
buf.write(sql + ')');
|
||||
});
|
||||
buf.write(';');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
static Foot parseRow(List row) {
|
||||
var result = new Foot.fromJson({
|
||||
'id': row[0].toString(),
|
||||
'leg_id': row[1],
|
||||
'n_toes': row[2],
|
||||
'created_at': row[3],
|
||||
'updated_at': row[4]
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
Stream<Foot> get(PostgreSQLConnection connection) {
|
||||
StreamController<Foot> ctrl = new StreamController<Foot>();
|
||||
connection.query(toSql()).then((rows) async {
|
||||
var futures = rows.map((row) async {
|
||||
var parsed = parseRow(row);
|
||||
return parsed;
|
||||
});
|
||||
var output = await Future.wait(futures);
|
||||
output.forEach(ctrl.add);
|
||||
ctrl.close();
|
||||
}).catchError(ctrl.addError);
|
||||
return ctrl.stream;
|
||||
}
|
||||
|
||||
static Future<Foot> getOne(int id, PostgreSQLConnection connection) {
|
||||
var query = new FootQuery();
|
||||
query.where.id.equals(id);
|
||||
return query.get(connection).first.catchError((_) => null);
|
||||
}
|
||||
|
||||
Stream<Foot> update(PostgreSQLConnection connection,
|
||||
{int legId, int nToes, DateTime createdAt, DateTime updatedAt}) {
|
||||
var buf = new StringBuffer(
|
||||
'UPDATE "foots" SET ("leg_id", "n_toes", "created_at", "updated_at") = (@legId, @nToes, @createdAt, @updatedAt) ');
|
||||
var whereClause = where.toWhereClause();
|
||||
if (whereClause != null) {
|
||||
buf.write(whereClause);
|
||||
}
|
||||
var __ormNow__ = new DateTime.now();
|
||||
var ctrl = new StreamController<Foot>();
|
||||
connection.query(
|
||||
buf.toString() +
|
||||
' RETURNING "id", "leg_id", "n_toes", "created_at", "updated_at";',
|
||||
substitutionValues: {
|
||||
'legId': legId,
|
||||
'nToes': nToes,
|
||||
'createdAt': createdAt != null ? createdAt : __ormNow__,
|
||||
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
|
||||
}).then((rows) async {
|
||||
var futures = rows.map((row) async {
|
||||
var parsed = parseRow(row);
|
||||
return parsed;
|
||||
});
|
||||
var output = await Future.wait(futures);
|
||||
output.forEach(ctrl.add);
|
||||
ctrl.close();
|
||||
}).catchError(ctrl.addError);
|
||||
return ctrl.stream;
|
||||
}
|
||||
|
||||
Stream<Foot> delete(PostgreSQLConnection connection) {
|
||||
StreamController<Foot> ctrl = new StreamController<Foot>();
|
||||
connection
|
||||
.query(toSql('DELETE FROM "foots"') +
|
||||
' RETURNING "id", "leg_id", "n_toes", "created_at", "updated_at";')
|
||||
.then((rows) async {
|
||||
var futures = rows.map((row) async {
|
||||
var parsed = parseRow(row);
|
||||
return parsed;
|
||||
});
|
||||
var output = await Future.wait(futures);
|
||||
output.forEach(ctrl.add);
|
||||
ctrl.close();
|
||||
}).catchError(ctrl.addError);
|
||||
return ctrl.stream;
|
||||
}
|
||||
|
||||
static Future<Foot> deleteOne(int id, PostgreSQLConnection connection) {
|
||||
var query = new FootQuery();
|
||||
query.where.id.equals(id);
|
||||
return query.delete(connection).first;
|
||||
}
|
||||
|
||||
static Future<Foot> insert(PostgreSQLConnection connection,
|
||||
{int legId, int nToes, DateTime createdAt, DateTime updatedAt}) async {
|
||||
var __ormNow__ = new DateTime.now();
|
||||
var result = await connection.query(
|
||||
'INSERT INTO "foots" ("leg_id", "n_toes", "created_at", "updated_at") VALUES (@legId, @nToes, @createdAt, @updatedAt) RETURNING "id", "leg_id", "n_toes", "created_at", "updated_at";',
|
||||
substitutionValues: {
|
||||
'legId': legId,
|
||||
'nToes': nToes,
|
||||
'createdAt': createdAt != null ? createdAt : __ormNow__,
|
||||
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
|
||||
});
|
||||
var output = parseRow(result[0]);
|
||||
return output;
|
||||
}
|
||||
|
||||
static Future<Foot> insertFoot(PostgreSQLConnection connection, Foot foot) {
|
||||
return FootQuery.insert(connection,
|
||||
legId: foot.legId,
|
||||
nToes: foot.nToes,
|
||||
createdAt: foot.createdAt,
|
||||
updatedAt: foot.updatedAt);
|
||||
}
|
||||
|
||||
static Future<Foot> updateFoot(PostgreSQLConnection connection, Foot foot) {
|
||||
var query = new FootQuery();
|
||||
query.where.id.equals(int.parse(foot.id));
|
||||
return query
|
||||
.update(connection,
|
||||
legId: foot.legId,
|
||||
nToes: foot.nToes,
|
||||
createdAt: foot.createdAt,
|
||||
updatedAt: foot.updatedAt)
|
||||
.first;
|
||||
}
|
||||
|
||||
static Stream<Foot> getAll(PostgreSQLConnection connection) =>
|
||||
new FootQuery().get(connection);
|
||||
}
|
||||
|
||||
class FootQueryWhere {
|
||||
final NumericSqlExpressionBuilder<int> id =
|
||||
new NumericSqlExpressionBuilder<int>();
|
||||
|
||||
final NumericSqlExpressionBuilder<int> legId =
|
||||
new NumericSqlExpressionBuilder<int>();
|
||||
|
||||
final NumericSqlExpressionBuilder<int> nToes =
|
||||
new NumericSqlExpressionBuilder<int>();
|
||||
|
||||
final DateTimeSqlExpressionBuilder createdAt =
|
||||
new DateTimeSqlExpressionBuilder('foots.created_at');
|
||||
|
||||
final DateTimeSqlExpressionBuilder updatedAt =
|
||||
new DateTimeSqlExpressionBuilder('foots.updated_at');
|
||||
|
||||
String toWhereClause({bool keyword}) {
|
||||
final List<String> expressions = [];
|
||||
if (id.hasValue) {
|
||||
expressions.add('foots.id ' + id.compile());
|
||||
}
|
||||
if (legId.hasValue) {
|
||||
expressions.add('foots.leg_id ' + legId.compile());
|
||||
}
|
||||
if (nToes.hasValue) {
|
||||
expressions.add('foots.n_toes ' + nToes.compile());
|
||||
}
|
||||
if (createdAt.hasValue) {
|
||||
expressions.add(createdAt.compile());
|
||||
}
|
||||
if (updatedAt.hasValue) {
|
||||
expressions.add(updatedAt.compile());
|
||||
}
|
||||
return expressions.isEmpty
|
||||
? null
|
||||
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
|
||||
}
|
||||
}
|
8
angel_orm_generator/test/models/foot.up.g.sql
Normal file
8
angel_orm_generator/test/models/foot.up.g.sql
Normal file
|
@ -0,0 +1,8 @@
|
|||
CREATE TEMPORARY TABLE "foots" (
|
||||
"id" serial,
|
||||
"leg_id" int,
|
||||
"n_toes" int,
|
||||
"created_at" timestamp,
|
||||
"updated_at" timestamp,
|
||||
PRIMARY KEY(id)
|
||||
);
|
16
angel_orm_generator/test/models/leg.dart
Normal file
16
angel_orm_generator/test/models/leg.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
library angel_orm_generator.test.models.leg;
|
||||
|
||||
import 'package:angel_model/angel_model.dart';
|
||||
import 'package:angel_orm/angel_orm.dart';
|
||||
import 'package:angel_serialize/angel_serialize.dart';
|
||||
import 'foot.dart';
|
||||
part 'leg.g.dart';
|
||||
|
||||
@serializable
|
||||
@orm
|
||||
class _Leg extends Model {
|
||||
@hasOne
|
||||
Foot foot;
|
||||
|
||||
String name;
|
||||
}
|
1
angel_orm_generator/test/models/leg.down.g.sql
Normal file
1
angel_orm_generator/test/models/leg.down.g.sql
Normal file
|
@ -0,0 +1 @@
|
|||
DROP TABLE "legs";
|
58
angel_orm_generator/test/models/leg.g.dart
Normal file
58
angel_orm_generator/test/models/leg.g.dart
Normal file
|
@ -0,0 +1,58 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of angel_orm_generator.test.models.leg;
|
||||
|
||||
// **************************************************************************
|
||||
// Generator: JsonModelGenerator
|
||||
// Target: class _Leg
|
||||
// **************************************************************************
|
||||
|
||||
class Leg extends _Leg {
|
||||
@override
|
||||
String id;
|
||||
|
||||
@override
|
||||
dynamic foot;
|
||||
|
||||
@override
|
||||
String name;
|
||||
|
||||
@override
|
||||
DateTime createdAt;
|
||||
|
||||
@override
|
||||
DateTime updatedAt;
|
||||
|
||||
Leg({this.id, this.foot, this.name, this.createdAt, this.updatedAt});
|
||||
|
||||
factory Leg.fromJson(Map data) {
|
||||
return new Leg(
|
||||
id: data['id'],
|
||||
foot: data['foot'],
|
||||
name: data['name'],
|
||||
createdAt: data['created_at'] is DateTime
|
||||
? data['created_at']
|
||||
: (data['created_at'] is String
|
||||
? DateTime.parse(data['created_at'])
|
||||
: null),
|
||||
updatedAt: data['updated_at'] is DateTime
|
||||
? data['updated_at']
|
||||
: (data['updated_at'] is String
|
||||
? DateTime.parse(data['updated_at'])
|
||||
: null));
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'foot': foot,
|
||||
'name': name,
|
||||
'created_at': createdAt == null ? null : createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt == null ? null : updatedAt.toIso8601String()
|
||||
};
|
||||
|
||||
static Leg parse(Map map) => new Leg.fromJson(map);
|
||||
|
||||
Leg clone() {
|
||||
return new Leg.fromJson(toJson());
|
||||
}
|
||||
}
|
258
angel_orm_generator/test/models/leg.orm.g.dart
Normal file
258
angel_orm_generator/test/models/leg.orm.g.dart
Normal file
|
@ -0,0 +1,258 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
// **************************************************************************
|
||||
// Generator: PostgresORMGenerator
|
||||
// Target: class _Leg
|
||||
// **************************************************************************
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angel_orm/angel_orm.dart';
|
||||
import 'package:postgres/postgres.dart';
|
||||
import 'leg.dart';
|
||||
import 'foot.orm.g.dart';
|
||||
|
||||
class LegQuery {
|
||||
final Map<LegQuery, bool> _unions = {};
|
||||
|
||||
String _sortKey;
|
||||
|
||||
String _sortMode;
|
||||
|
||||
int limit;
|
||||
|
||||
int offset;
|
||||
|
||||
final List<LegQueryWhere> _or = [];
|
||||
|
||||
final LegQueryWhere where = new LegQueryWhere();
|
||||
|
||||
void union(LegQuery query) {
|
||||
_unions[query] = false;
|
||||
}
|
||||
|
||||
void unionAll(LegQuery query) {
|
||||
_unions[query] = true;
|
||||
}
|
||||
|
||||
void sortDescending(String key) {
|
||||
_sortMode = 'Descending';
|
||||
_sortKey = ('legs.' + key);
|
||||
}
|
||||
|
||||
void sortAscending(String key) {
|
||||
_sortMode = 'Ascending';
|
||||
_sortKey = ('legs.' + key);
|
||||
}
|
||||
|
||||
void or(LegQueryWhere selector) {
|
||||
_or.add(selector);
|
||||
}
|
||||
|
||||
String toSql([String prefix]) {
|
||||
var buf = new StringBuffer();
|
||||
buf.write(prefix != null
|
||||
? prefix
|
||||
: 'SELECT legs.id, legs.name, legs.created_at, legs.updated_at, foots.id, foots.leg_id, foots.n_toes, foots.created_at, foots.updated_at FROM "legs"');
|
||||
if (prefix == null) {
|
||||
buf.write(' LEFT OUTER JOIN foots ON legs.id = foots.leg_id');
|
||||
}
|
||||
var whereClause = where.toWhereClause();
|
||||
if (whereClause != null) {
|
||||
buf.write(' ' + whereClause);
|
||||
}
|
||||
_or.forEach((x) {
|
||||
var whereClause = x.toWhereClause(keyword: false);
|
||||
if (whereClause != null) {
|
||||
buf.write(' OR (' + whereClause + ')');
|
||||
}
|
||||
});
|
||||
if (prefix == null) {
|
||||
if (limit != null) {
|
||||
buf.write(' LIMIT ' + limit.toString());
|
||||
}
|
||||
if (offset != null) {
|
||||
buf.write(' OFFSET ' + offset.toString());
|
||||
}
|
||||
if (_sortMode == 'Descending') {
|
||||
buf.write(' ORDER BY "' + _sortKey + '" DESC');
|
||||
}
|
||||
if (_sortMode == 'Ascending') {
|
||||
buf.write(' ORDER BY "' + _sortKey + '" ASC');
|
||||
}
|
||||
_unions.forEach((query, all) {
|
||||
buf.write(' UNION');
|
||||
if (all) {
|
||||
buf.write(' ALL');
|
||||
}
|
||||
buf.write(' (');
|
||||
var sql = query.toSql().replaceAll(';', '');
|
||||
buf.write(sql + ')');
|
||||
});
|
||||
buf.write(';');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
static Leg parseRow(List row) {
|
||||
var result = new Leg.fromJson({
|
||||
'id': row[0].toString(),
|
||||
'name': row[1],
|
||||
'created_at': row[2],
|
||||
'updated_at': row[3]
|
||||
});
|
||||
if (row.length > 4) {
|
||||
result.foot =
|
||||
FootQuery.parseRow([row[4], row[5], row[6], row[7], row[8]]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Stream<Leg> get(PostgreSQLConnection connection) {
|
||||
StreamController<Leg> ctrl = new StreamController<Leg>();
|
||||
connection.query(toSql()).then((rows) async {
|
||||
var futures = rows.map((row) async {
|
||||
var parsed = parseRow(row);
|
||||
var footQuery = new FootQuery();
|
||||
footQuery.where.id.equals(row[0]);
|
||||
parsed.foot =
|
||||
await footQuery.get(connection).first.catchError((_) => null);
|
||||
return parsed;
|
||||
});
|
||||
var output = await Future.wait(futures);
|
||||
output.forEach(ctrl.add);
|
||||
ctrl.close();
|
||||
}).catchError(ctrl.addError);
|
||||
return ctrl.stream;
|
||||
}
|
||||
|
||||
static Future<Leg> getOne(int id, PostgreSQLConnection connection) {
|
||||
var query = new LegQuery();
|
||||
query.where.id.equals(id);
|
||||
return query.get(connection).first.catchError((_) => null);
|
||||
}
|
||||
|
||||
Stream<Leg> update(PostgreSQLConnection connection,
|
||||
{String name, DateTime createdAt, DateTime updatedAt}) {
|
||||
var buf = new StringBuffer(
|
||||
'UPDATE "legs" SET ("name", "created_at", "updated_at") = (@name, @createdAt, @updatedAt) ');
|
||||
var whereClause = where.toWhereClause();
|
||||
if (whereClause != null) {
|
||||
buf.write(whereClause);
|
||||
}
|
||||
var __ormNow__ = new DateTime.now();
|
||||
var ctrl = new StreamController<Leg>();
|
||||
connection.query(
|
||||
buf.toString() + ' RETURNING "id", "name", "created_at", "updated_at";',
|
||||
substitutionValues: {
|
||||
'name': name,
|
||||
'createdAt': createdAt != null ? createdAt : __ormNow__,
|
||||
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
|
||||
}).then((rows) async {
|
||||
var futures = rows.map((row) async {
|
||||
var parsed = parseRow(row);
|
||||
var footQuery = new FootQuery();
|
||||
footQuery.where.id.equals(row[0]);
|
||||
parsed.foot =
|
||||
await footQuery.get(connection).first.catchError((_) => null);
|
||||
return parsed;
|
||||
});
|
||||
var output = await Future.wait(futures);
|
||||
output.forEach(ctrl.add);
|
||||
ctrl.close();
|
||||
}).catchError(ctrl.addError);
|
||||
return ctrl.stream;
|
||||
}
|
||||
|
||||
Stream<Leg> delete(PostgreSQLConnection connection) {
|
||||
StreamController<Leg> ctrl = new StreamController<Leg>();
|
||||
connection
|
||||
.query(toSql('DELETE FROM "legs"') +
|
||||
' RETURNING "id", "name", "created_at", "updated_at";')
|
||||
.then((rows) async {
|
||||
var futures = rows.map((row) async {
|
||||
var parsed = parseRow(row);
|
||||
var footQuery = new FootQuery();
|
||||
footQuery.where.id.equals(row[0]);
|
||||
parsed.foot =
|
||||
await footQuery.get(connection).first.catchError((_) => null);
|
||||
return parsed;
|
||||
});
|
||||
var output = await Future.wait(futures);
|
||||
output.forEach(ctrl.add);
|
||||
ctrl.close();
|
||||
}).catchError(ctrl.addError);
|
||||
return ctrl.stream;
|
||||
}
|
||||
|
||||
static Future<Leg> deleteOne(int id, PostgreSQLConnection connection) {
|
||||
var query = new LegQuery();
|
||||
query.where.id.equals(id);
|
||||
return query.delete(connection).first;
|
||||
}
|
||||
|
||||
static Future<Leg> insert(PostgreSQLConnection connection,
|
||||
{String name, DateTime createdAt, DateTime updatedAt}) async {
|
||||
var __ormNow__ = new DateTime.now();
|
||||
var result = await connection.query(
|
||||
'INSERT INTO "legs" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt) RETURNING "id", "name", "created_at", "updated_at";',
|
||||
substitutionValues: {
|
||||
'name': name,
|
||||
'createdAt': createdAt != null ? createdAt : __ormNow__,
|
||||
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
|
||||
});
|
||||
var output = parseRow(result[0]);
|
||||
var footQuery = new FootQuery();
|
||||
footQuery.where.id.equals(result[0][0]);
|
||||
output.foot = await footQuery.get(connection).first.catchError((_) => null);
|
||||
return output;
|
||||
}
|
||||
|
||||
static Future<Leg> insertLeg(PostgreSQLConnection connection, Leg leg) {
|
||||
return LegQuery.insert(connection,
|
||||
name: leg.name, createdAt: leg.createdAt, updatedAt: leg.updatedAt);
|
||||
}
|
||||
|
||||
static Future<Leg> updateLeg(PostgreSQLConnection connection, Leg leg) {
|
||||
var query = new LegQuery();
|
||||
query.where.id.equals(int.parse(leg.id));
|
||||
return query
|
||||
.update(connection,
|
||||
name: leg.name, createdAt: leg.createdAt, updatedAt: leg.updatedAt)
|
||||
.first;
|
||||
}
|
||||
|
||||
static Stream<Leg> getAll(PostgreSQLConnection connection) =>
|
||||
new LegQuery().get(connection);
|
||||
}
|
||||
|
||||
class LegQueryWhere {
|
||||
final NumericSqlExpressionBuilder<int> id =
|
||||
new NumericSqlExpressionBuilder<int>();
|
||||
|
||||
final StringSqlExpressionBuilder name = new StringSqlExpressionBuilder();
|
||||
|
||||
final DateTimeSqlExpressionBuilder createdAt =
|
||||
new DateTimeSqlExpressionBuilder('legs.created_at');
|
||||
|
||||
final DateTimeSqlExpressionBuilder updatedAt =
|
||||
new DateTimeSqlExpressionBuilder('legs.updated_at');
|
||||
|
||||
String toWhereClause({bool keyword}) {
|
||||
final List<String> expressions = [];
|
||||
if (id.hasValue) {
|
||||
expressions.add('legs.id ' + id.compile());
|
||||
}
|
||||
if (name.hasValue) {
|
||||
expressions.add('legs.name ' + name.compile());
|
||||
}
|
||||
if (createdAt.hasValue) {
|
||||
expressions.add(createdAt.compile());
|
||||
}
|
||||
if (updatedAt.hasValue) {
|
||||
expressions.add(updatedAt.compile());
|
||||
}
|
||||
return expressions.isEmpty
|
||||
? null
|
||||
: ((keyword != false ? 'WHERE ' : '') + expressions.join(' AND '));
|
||||
}
|
||||
}
|
7
angel_orm_generator/test/models/leg.up.g.sql
Normal file
7
angel_orm_generator/test/models/leg.up.g.sql
Normal file
|
@ -0,0 +1,7 @@
|
|||
CREATE TEMPORARY TABLE "legs" (
|
||||
"id" serial,
|
||||
"name" varchar,
|
||||
"created_at" timestamp,
|
||||
"updated_at" timestamp,
|
||||
PRIMARY KEY(id)
|
||||
);
|
|
@ -125,9 +125,7 @@ class RoleQuery {
|
|||
var buf = new StringBuffer(
|
||||
'UPDATE "roles" SET ("name", "created_at", "updated_at") = (@name, @createdAt, @updatedAt) ');
|
||||
var whereClause = where.toWhereClause();
|
||||
if (whereClause == null) {
|
||||
buf.write('WHERE "id" = @id');
|
||||
} else {
|
||||
if (whereClause != null) {
|
||||
buf.write(whereClause);
|
||||
}
|
||||
var __ormNow__ = new DateTime.now();
|
||||
|
|
|
@ -53,9 +53,7 @@ class UserQuery {
|
|||
buf.write(prefix != null
|
||||
? prefix
|
||||
: 'SELECT users.id, users.username, users.password, users.email, users.created_at, users.updated_at, roles.id, roles.name, roles.created_at, roles.updated_at FROM "users"');
|
||||
if (prefix == null) {
|
||||
buf.write(' INNER JOIN roles ON users.role_id = roles.id');
|
||||
}
|
||||
if (prefix == null) {}
|
||||
var whereClause = where.toWhereClause();
|
||||
if (whereClause != null) {
|
||||
buf.write(' ' + whereClause);
|
||||
|
@ -140,9 +138,7 @@ class UserQuery {
|
|||
var buf = new StringBuffer(
|
||||
'UPDATE "users" SET ("username", "password", "email", "created_at", "updated_at") = (@username, @password, @email, @createdAt, @updatedAt) ');
|
||||
var whereClause = where.toWhereClause();
|
||||
if (whereClause == null) {
|
||||
buf.write('WHERE "id" = @id');
|
||||
} else {
|
||||
if (whereClause != null) {
|
||||
buf.write(whereClause);
|
||||
}
|
||||
var __ormNow__ = new DateTime.now();
|
||||
|
|
|
@ -51,7 +51,7 @@ main() {
|
|||
PostgreSQLConnection connection;
|
||||
|
||||
setUp(() async {
|
||||
connection = await connectToPostgres();
|
||||
connection = await connectToPostgres(['car']);
|
||||
});
|
||||
|
||||
group('selects', () {
|
||||
|
|
|
@ -6,12 +6,16 @@ import 'package:angel_serialize_generator/angel_serialize_generator.dart';
|
|||
final InputSet ALL_MODELS =
|
||||
new InputSet('angel_orm_generator', const ['test/models/*.dart']);
|
||||
final InputSet STANDALONE_MODELS = new InputSet('angel_orm_generator', const [
|
||||
'test/models/car.dart',
|
||||
'test/models/author.dart',
|
||||
'test/models/car.dart',
|
||||
'test/models/foot.dart',
|
||||
'test/models/role.dart'
|
||||
]);
|
||||
final InputSet DEPENDENT_MODELS = new InputSet('angel_orm_generator',
|
||||
const ['test/models/book.dart', 'test/models/user.dart']);
|
||||
final InputSet DEPENDENT_MODELS = new InputSet('angel_orm_generator', const [
|
||||
'test/models/book.dart',
|
||||
'test/models/leg.dart',
|
||||
'test/models/user.dart'
|
||||
]);
|
||||
|
||||
final PhaseGroup PHASES = new PhaseGroup()
|
||||
..addPhase(new Phase()
|
||||
|
|
Loading…
Reference in a new issue