alpha
This commit is contained in:
parent
d8304b9104
commit
160c620f57
14 changed files with 535 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,6 +5,7 @@
|
||||||
.packages
|
.packages
|
||||||
.project
|
.project
|
||||||
.pub/
|
.pub/
|
||||||
|
.scripts-bin/
|
||||||
build/
|
build/
|
||||||
**/packages/
|
**/packages/
|
||||||
|
|
||||||
|
|
1
.travis.yml
Normal file
1
.travis.yml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
language: dart
|
21
README.md
21
README.md
|
@ -1,2 +1,23 @@
|
||||||
# relations
|
# relations
|
||||||
|
[![version 1.0.0-alpha](https://img.shields.io/badge/pub-v1.0.0--alpha-red.svg)](https://pub.dartlang.org/packages/angel_relations)
|
||||||
|
[![build status](https://travis-ci.org/angel-dart/relations.svg)](https://travis-ci.org/angel-dart/relations)
|
||||||
|
|
||||||
Database-agnostic relations between Angel services.
|
Database-agnostic relations between Angel services.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Authors owning one book
|
||||||
|
app.service('authors').afterAll(
|
||||||
|
relations.hasOne('books', as: 'book', foreignKey: 'authorId'));
|
||||||
|
|
||||||
|
// Or multiple
|
||||||
|
app.service('authors').afterAll(
|
||||||
|
relations.hasMany('books', foreignKey: 'authorId'));
|
||||||
|
|
||||||
|
// Or, books belonging to authors
|
||||||
|
app.service('books').afterAll(relations.belongsTo('authors'));
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently supports:
|
||||||
|
* `hasOne`
|
||||||
|
* `hasMany`
|
||||||
|
* `belongsTo`
|
7
lib/angel_relations.dart
Normal file
7
lib/angel_relations.dart
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
/// Hooks to populate data returned from services, in a fashion
|
||||||
|
/// reminiscent of a relational database.
|
||||||
|
library angel_relations;
|
||||||
|
|
||||||
|
export 'src/belongs_to.dart';
|
||||||
|
export 'src/has_many.dart';
|
||||||
|
export 'src/has_one.dart';
|
79
lib/src/belongs_to.dart
Normal file
79
lib/src/belongs_to.dart
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:mirrors';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'plural.dart' as pluralize;
|
||||||
|
import 'no_service.dart';
|
||||||
|
|
||||||
|
/// Represents a relationship in which the current [service] "belongs to"
|
||||||
|
/// a single member of the service at [servicePath]. Use [as] to set the name
|
||||||
|
/// on the target object.
|
||||||
|
///
|
||||||
|
/// Defaults:
|
||||||
|
/// * [foreignKey]: `userId`
|
||||||
|
/// * [localKey]: `id`
|
||||||
|
HookedServiceEventListener belongsTo(Pattern servicePath,
|
||||||
|
{String as,
|
||||||
|
String foreignKey,
|
||||||
|
String localKey,
|
||||||
|
getForeignKey(obj),
|
||||||
|
assignForeignObject(foreign, obj)}) {
|
||||||
|
return (HookedServiceEvent e) async {
|
||||||
|
var ref = e.service.app.service(servicePath);
|
||||||
|
var foreignName = as?.isNotEmpty == true
|
||||||
|
? as
|
||||||
|
: pluralize.singular(servicePath.toString());
|
||||||
|
if (ref == null) throw noService(servicePath);
|
||||||
|
|
||||||
|
String localId = localKey;
|
||||||
|
|
||||||
|
if (localId == null) {
|
||||||
|
localId = foreignName + 'Id';
|
||||||
|
print('No local key provided for belongsTo, defaulting to \'$localId\'.');
|
||||||
|
}
|
||||||
|
|
||||||
|
_getForeignKey(obj) {
|
||||||
|
if (getForeignKey != null)
|
||||||
|
return getForeignKey(obj);
|
||||||
|
else if (obj is Map)
|
||||||
|
return obj[localId];
|
||||||
|
else if (obj is Extensible)
|
||||||
|
return obj.properties[localId];
|
||||||
|
else if (localId == null || localId == 'userId')
|
||||||
|
return obj.userId;
|
||||||
|
else
|
||||||
|
return reflect(obj).getField(new Symbol(localId)).reflectee;
|
||||||
|
}
|
||||||
|
|
||||||
|
_assignForeignObject(foreign, obj) {
|
||||||
|
if (assignForeignObject != null)
|
||||||
|
return assignForeignObject(foreign, obj);
|
||||||
|
else if (obj is Map)
|
||||||
|
obj[foreignName] = foreign;
|
||||||
|
else if (obj is Extensible)
|
||||||
|
obj.properties[foreignName] = foreign;
|
||||||
|
else
|
||||||
|
reflect(obj).setField(new Symbol(foreignName), foreign);
|
||||||
|
}
|
||||||
|
|
||||||
|
_normalize(obj) async {
|
||||||
|
if (obj != null) {
|
||||||
|
var id = await _getForeignKey(obj);
|
||||||
|
var indexed = await ref.index({
|
||||||
|
'query': {foreignKey ?? 'id': id}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (indexed?.isNotEmpty != true) {
|
||||||
|
await _assignForeignObject(null, obj);
|
||||||
|
} else {
|
||||||
|
var child = indexed.first;
|
||||||
|
await _assignForeignObject(child, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.result is Iterable) {
|
||||||
|
await Future.wait(e.result.map(_normalize));
|
||||||
|
} else
|
||||||
|
await _normalize(e.result);
|
||||||
|
};
|
||||||
|
}
|
74
lib/src/has_many.dart
Normal file
74
lib/src/has_many.dart
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:mirrors';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'plural.dart' as pluralize;
|
||||||
|
import 'no_service.dart';
|
||||||
|
|
||||||
|
/// Represents a relationship in which the current [service] "owns"
|
||||||
|
/// members of the service at [servicePath]. Use [as] to set the name
|
||||||
|
/// on the target object.
|
||||||
|
///
|
||||||
|
/// Defaults:
|
||||||
|
/// * [foreignKey]: `userId`
|
||||||
|
/// * [localKey]: `id`
|
||||||
|
HookedServiceEventListener hasMany(Pattern servicePath,
|
||||||
|
{String as,
|
||||||
|
String foreignKey,
|
||||||
|
String localKey,
|
||||||
|
getLocalKey(obj),
|
||||||
|
assignForeignObjects(foreign, obj)}) {
|
||||||
|
return (HookedServiceEvent e) async {
|
||||||
|
var ref = e.service.app.service(servicePath);
|
||||||
|
var foreignName =
|
||||||
|
as?.isNotEmpty == true ? as : pluralize.plural(servicePath.toString());
|
||||||
|
if (ref == null) throw noService(servicePath);
|
||||||
|
|
||||||
|
if (foreignKey == null)
|
||||||
|
print(
|
||||||
|
'WARNING: No foreign key provided for hasMany, defaulting to \'userId\'.');
|
||||||
|
|
||||||
|
_getLocalKey(obj) {
|
||||||
|
if (getLocalKey != null)
|
||||||
|
return getLocalKey(obj);
|
||||||
|
else if (obj is Map)
|
||||||
|
return obj[localKey ?? 'id'];
|
||||||
|
else if (obj is Extensible)
|
||||||
|
return obj.properties[localKey ?? 'id'];
|
||||||
|
else if (localKey == null || localKey == 'id')
|
||||||
|
return obj.id;
|
||||||
|
else
|
||||||
|
return reflect(obj).getField(new Symbol(localKey ?? 'id')).reflectee;
|
||||||
|
}
|
||||||
|
|
||||||
|
_assignForeignObjects(foreign, obj) {
|
||||||
|
if (assignForeignObjects != null)
|
||||||
|
return assignForeignObjects(foreign, obj);
|
||||||
|
else if (obj is Map)
|
||||||
|
obj[foreignName] = foreign;
|
||||||
|
else if (obj is Extensible)
|
||||||
|
obj.properties[foreignName] = foreign;
|
||||||
|
else
|
||||||
|
reflect(obj).setField(new Symbol(foreignName), foreign);
|
||||||
|
}
|
||||||
|
|
||||||
|
_normalize(obj) async {
|
||||||
|
if (obj != null) {
|
||||||
|
var id = await _getLocalKey(obj);
|
||||||
|
var indexed = await ref.index({
|
||||||
|
'query': {foreignKey ?? 'userId': id}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (indexed?.isNotEmpty != true) {
|
||||||
|
await _assignForeignObjects([], obj);
|
||||||
|
} else {
|
||||||
|
await _assignForeignObjects(indexed, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.result is Iterable) {
|
||||||
|
await Future.wait(e.result.map(_normalize));
|
||||||
|
} else
|
||||||
|
await _normalize(e.result);
|
||||||
|
};
|
||||||
|
}
|
76
lib/src/has_one.dart
Normal file
76
lib/src/has_one.dart
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:mirrors';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'plural.dart' as pluralize;
|
||||||
|
import 'no_service.dart';
|
||||||
|
|
||||||
|
/// Represents a relationship in which the current [service] "owns"
|
||||||
|
/// a single member of the service at [servicePath]. Use [as] to set the name
|
||||||
|
/// on the target object.
|
||||||
|
///
|
||||||
|
/// Defaults:
|
||||||
|
/// * [foreignKey]: `userId`
|
||||||
|
/// * [localKey]: `id`
|
||||||
|
HookedServiceEventListener hasOne(Pattern servicePath,
|
||||||
|
{String as,
|
||||||
|
String foreignKey,
|
||||||
|
String localKey,
|
||||||
|
getLocalKey(obj),
|
||||||
|
assignForeignObject(foreign, obj)}) {
|
||||||
|
return (HookedServiceEvent e) async {
|
||||||
|
var ref = e.service.app.service(servicePath);
|
||||||
|
var foreignName = as?.isNotEmpty == true
|
||||||
|
? as
|
||||||
|
: pluralize.singular(servicePath.toString());
|
||||||
|
if (ref == null) throw noService(servicePath);
|
||||||
|
|
||||||
|
if (foreignKey == null)
|
||||||
|
print(
|
||||||
|
'WARNING: No foreign key provided for hasOne, defaulting to \'userId\'.');
|
||||||
|
|
||||||
|
_getLocalKey(obj) {
|
||||||
|
if (getLocalKey != null)
|
||||||
|
return getLocalKey(obj);
|
||||||
|
else if (obj is Map)
|
||||||
|
return obj[localKey ?? 'id'];
|
||||||
|
else if (obj is Extensible)
|
||||||
|
return obj.properties[localKey ?? 'id'];
|
||||||
|
else if (localKey == null || localKey == 'id')
|
||||||
|
return obj.id;
|
||||||
|
else
|
||||||
|
return reflect(obj).getField(new Symbol(localKey ?? 'id')).reflectee;
|
||||||
|
}
|
||||||
|
|
||||||
|
_assignForeignObject(foreign, obj) {
|
||||||
|
if (assignForeignObject != null)
|
||||||
|
return assignForeignObject(foreign, obj);
|
||||||
|
else if (obj is Map)
|
||||||
|
obj[foreignName] = foreign;
|
||||||
|
else if (obj is Extensible)
|
||||||
|
obj.properties[foreignName] = foreign;
|
||||||
|
else
|
||||||
|
reflect(obj).setField(new Symbol(foreignName), foreign);
|
||||||
|
}
|
||||||
|
|
||||||
|
_normalize(obj) async {
|
||||||
|
if (obj != null) {
|
||||||
|
var id = await _getLocalKey(obj);
|
||||||
|
var indexed = await ref.index({
|
||||||
|
'query': {foreignKey ?? 'userId': id}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (indexed?.isNotEmpty != true) {
|
||||||
|
await _assignForeignObject(null, obj);
|
||||||
|
} else {
|
||||||
|
var child = indexed.first;
|
||||||
|
await _assignForeignObject(child, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.result is Iterable) {
|
||||||
|
await Future.wait(e.result.map(_normalize));
|
||||||
|
} else
|
||||||
|
await _normalize(e.result);
|
||||||
|
};
|
||||||
|
}
|
2
lib/src/no_service.dart
Normal file
2
lib/src/no_service.dart
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ArgumentError noService(Pattern path) =>
|
||||||
|
new ArgumentError("No service exists at path '$path'.");
|
21
lib/src/plural.dart
Normal file
21
lib/src/plural.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
String singular(String path) {
|
||||||
|
var str = path.trim().split('/').where((str) => str.isNotEmpty).last;
|
||||||
|
|
||||||
|
if (str.endsWith('ies'))
|
||||||
|
return str.substring(0, str.length - 3) + 'y';
|
||||||
|
else if (str.endsWith('s'))
|
||||||
|
return str.substring(0, str.length - 1);
|
||||||
|
else
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
String plural(String path) {
|
||||||
|
var str = path.trim().split('/').where((str) => str.isNotEmpty).last;
|
||||||
|
|
||||||
|
if (str.endsWith('y'))
|
||||||
|
return str.substring(0, str.length - 1) + 'ies';
|
||||||
|
else if (str.endsWith('s'))
|
||||||
|
return str;
|
||||||
|
else
|
||||||
|
return str + 's';
|
||||||
|
}
|
12
pubspec.yaml
Normal file
12
pubspec.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
author: "Tobe O <thosakwe@gmail.com>"
|
||||||
|
description: "Database-agnostic relations between Angel services."
|
||||||
|
homepage: "https://github.com/angel-dart/relations.git"
|
||||||
|
name: "angel_relations"
|
||||||
|
version: 1.0.0-alpha
|
||||||
|
environment:
|
||||||
|
sdk: ">=1.19.0"
|
||||||
|
dependencies:
|
||||||
|
angel_framework: "^1.0.0-dev"
|
||||||
|
dev_dependencies:
|
||||||
|
angel_seeder: ^0.0.0
|
||||||
|
test: ^0.12.0
|
56
test/belongs_to_test.dart
Normal file
56
test/belongs_to_test.dart
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_relations/angel_relations.dart' as relations;
|
||||||
|
import 'package:angel_seeder/angel_seeder.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Angel app;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
app = new Angel()
|
||||||
|
..use('/authors', new MapService())
|
||||||
|
..use('/books', new MapService());
|
||||||
|
|
||||||
|
await app.configure(seed(
|
||||||
|
'authors',
|
||||||
|
new SeederConfiguration<Map>(
|
||||||
|
count: 10,
|
||||||
|
template: {'name': (Faker faker) => faker.person.name()},
|
||||||
|
callback: (Map author, seed) {
|
||||||
|
return seed(
|
||||||
|
'books',
|
||||||
|
new SeederConfiguration(delete: false, count: 10, template: {
|
||||||
|
'authorId': author['id'],
|
||||||
|
'title': (Faker faker) =>
|
||||||
|
'I love to eat ${faker.food.dish()}'
|
||||||
|
}));
|
||||||
|
})));
|
||||||
|
|
||||||
|
app.service('books').afterAll(relations.belongsTo('authors'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index', () async {
|
||||||
|
var books = await app.service('books').index();
|
||||||
|
print(books);
|
||||||
|
|
||||||
|
expect(books, allOf(isList, isNotEmpty));
|
||||||
|
|
||||||
|
for (Map book in books) {
|
||||||
|
expect(book.keys, contains('author'));
|
||||||
|
|
||||||
|
Map author = book['author'];
|
||||||
|
expect(author['id'], equals(book['authorId']));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create', () async {
|
||||||
|
var warAndPeace = await app
|
||||||
|
.service('books')
|
||||||
|
.create(new Book(title: 'War and Peace').toJson());
|
||||||
|
|
||||||
|
print(warAndPeace);
|
||||||
|
expect(warAndPeace.keys, contains('author'));
|
||||||
|
expect(warAndPeace['author'], isNull);
|
||||||
|
});
|
||||||
|
}
|
67
test/common.dart
Normal file
67
test/common.dart
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:json_god/json_god.dart' as god;
|
||||||
|
|
||||||
|
class MapService extends Service {
|
||||||
|
final List<Map> _items = [];
|
||||||
|
|
||||||
|
Iterable<Map> tailor(Iterable<Map> items, Map params) {
|
||||||
|
if (params == null) return items;
|
||||||
|
|
||||||
|
var r = items;
|
||||||
|
|
||||||
|
if (params != null && params['query'] is Map) {
|
||||||
|
Map query = params['query'];
|
||||||
|
|
||||||
|
for (var key in query.keys) {
|
||||||
|
r = r.where((m) => m[key] == query[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
index([params]) async => tailor(_items, params).toList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
read(id, [Map params]) async {
|
||||||
|
return tailor(_items, params).firstWhere((m) => m['id'] == id,
|
||||||
|
orElse: () => throw new AngelHttpException.notFound());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
create(data, [params]) async {
|
||||||
|
Map d = data is Map ? data : god.serializeObject(data);
|
||||||
|
d['id'] = _items.length.toString();
|
||||||
|
_items.add(d);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
remove(id, [params]) async {
|
||||||
|
if (id == null) _items.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Author {
|
||||||
|
String id, name;
|
||||||
|
|
||||||
|
Author({this.id, this.name});
|
||||||
|
|
||||||
|
Map toJson() => {'id': id, 'name': name};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Book {
|
||||||
|
String authorId, title;
|
||||||
|
|
||||||
|
Book({this.authorId, this.title});
|
||||||
|
|
||||||
|
Map toJson() => {'authorId': authorId, 'title': title};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Chapter {
|
||||||
|
String bookId, title;
|
||||||
|
int pageCount;
|
||||||
|
|
||||||
|
Chapter({this.bookId, this.title, this.pageCount});
|
||||||
|
}
|
61
test/has_many_test.dart
Normal file
61
test/has_many_test.dart
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_relations/angel_relations.dart' as relations;
|
||||||
|
import 'package:angel_seeder/angel_seeder.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Angel app;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
app = new Angel()
|
||||||
|
..use('/authors', new MapService())
|
||||||
|
..use('/books', new MapService());
|
||||||
|
|
||||||
|
await app.configure(seed(
|
||||||
|
'authors',
|
||||||
|
new SeederConfiguration<Map>(
|
||||||
|
count: 10,
|
||||||
|
template: {'name': (Faker faker) => faker.person.name()},
|
||||||
|
callback: (Map author, seed) {
|
||||||
|
return seed(
|
||||||
|
'books',
|
||||||
|
new SeederConfiguration(delete: false, count: 10, template: {
|
||||||
|
'authorId': author['id'],
|
||||||
|
'title': (Faker faker) =>
|
||||||
|
'I love to eat ${faker.food.dish()}'
|
||||||
|
}));
|
||||||
|
})));
|
||||||
|
|
||||||
|
app
|
||||||
|
.service('authors')
|
||||||
|
.afterAll(relations.hasMany('books', foreignKey: 'authorId'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index', () async {
|
||||||
|
var authors = await app.service('authors').index();
|
||||||
|
print(authors);
|
||||||
|
|
||||||
|
expect(authors, allOf(isList, isNotEmpty));
|
||||||
|
|
||||||
|
for (Map author in authors) {
|
||||||
|
expect(author.keys, contains('books'));
|
||||||
|
|
||||||
|
List<Map> books = author['books'];
|
||||||
|
|
||||||
|
for (var book in books) {
|
||||||
|
expect(book['authorId'], equals(author['id']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create', () async {
|
||||||
|
var tolstoy = await app
|
||||||
|
.service('authors')
|
||||||
|
.create(new Author(name: 'Leo Tolstoy').toJson());
|
||||||
|
|
||||||
|
print(tolstoy);
|
||||||
|
expect(tolstoy.keys, contains('books'));
|
||||||
|
expect(tolstoy['books'], allOf(isList, isEmpty));
|
||||||
|
});
|
||||||
|
}
|
57
test/has_one_test.dart
Normal file
57
test/has_one_test.dart
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_relations/angel_relations.dart' as relations;
|
||||||
|
import 'package:angel_seeder/angel_seeder.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
Angel app;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
app = new Angel()
|
||||||
|
..use('/authors', new MapService())
|
||||||
|
..use('/books', new MapService());
|
||||||
|
|
||||||
|
await app.configure(seed(
|
||||||
|
'authors',
|
||||||
|
new SeederConfiguration<Map>(
|
||||||
|
count: 10,
|
||||||
|
template: {'name': (Faker faker) => faker.person.name()},
|
||||||
|
callback: (Map author, seed) {
|
||||||
|
return seed(
|
||||||
|
'books',
|
||||||
|
new SeederConfiguration(delete: false, count: 10, template: {
|
||||||
|
'authorId': author['id'],
|
||||||
|
'title': (Faker faker) =>
|
||||||
|
'I love to eat ${faker.food.dish()}'
|
||||||
|
}));
|
||||||
|
})));
|
||||||
|
|
||||||
|
app.service('authors').afterAll(
|
||||||
|
relations.hasOne('books', as: 'book', foreignKey: 'authorId'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index', () async {
|
||||||
|
var authors = await app.service('authors').index();
|
||||||
|
print(authors);
|
||||||
|
|
||||||
|
expect(authors, allOf(isList, isNotEmpty));
|
||||||
|
|
||||||
|
for (Map author in authors) {
|
||||||
|
expect(author.keys, contains('book'));
|
||||||
|
|
||||||
|
Map book = author['book'];
|
||||||
|
expect(book['authorId'], equals(author['id']));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create', () async {
|
||||||
|
var tolstoy = await app
|
||||||
|
.service('authors')
|
||||||
|
.create(new Author(name: 'Leo Tolstoy').toJson());
|
||||||
|
|
||||||
|
print(tolstoy);
|
||||||
|
expect(tolstoy.keys, contains('book'));
|
||||||
|
expect(tolstoy['book'], isNull);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue