Add 'packages/relations/' from commit 'f1d7081f462d497a1d5810356132867158379e79'
git-subtree-dir: packages/relations git-subtree-mainline:b1a6f262ea
git-subtree-split:f1d7081f46
This commit is contained in:
commit
cad3b9c746
17 changed files with 829 additions and 0 deletions
93
packages/relations/.gitignore
vendored
Normal file
93
packages/relations/.gitignore
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.buildlog
|
||||
.packages
|
||||
.project
|
||||
.pub/
|
||||
.scripts-bin/
|
||||
build/
|
||||
**/packages/
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
*.dart.js
|
||||
*.part.js
|
||||
*.js.deps
|
||||
*.js.map
|
||||
*.info.json
|
||||
|
||||
# Directory created by dartdoc
|
||||
doc/api/
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
pubspec.lock
|
||||
### Dart template
|
||||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
|
||||
# SDK 1.20 and later (no longer creates packages directories)
|
||||
|
||||
# Older SDK versions
|
||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
||||
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
|
||||
# Directory created by dartdoc
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
1
packages/relations/.travis.yml
Normal file
1
packages/relations/.travis.yml
Normal file
|
@ -0,0 +1 @@
|
|||
language: dart
|
21
packages/relations/LICENSE
Normal file
21
packages/relations/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 The Angel Framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
25
packages/relations/README.md
Normal file
25
packages/relations/README.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# relations
|
||||
[![version 1.0.1](https://img.shields.io/badge/pub-v1.0.1-brightgreen.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.
|
||||
|
||||
```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'));
|
||||
```
|
||||
|
||||
Supports:
|
||||
* `hasOne`
|
||||
* `hasMany`
|
||||
* `hasManyThrough`
|
||||
* `belongsTo`
|
||||
* `belongsToMany`
|
9
packages/relations/lib/angel_relations.dart
Normal file
9
packages/relations/lib/angel_relations.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
/// Hooks to populate data returned from services, in a fashion
|
||||
/// reminiscent of a relational database.
|
||||
library angel_relations;
|
||||
|
||||
export 'src/belongs_to_many.dart';
|
||||
export 'src/belongs_to.dart';
|
||||
export 'src/has_many.dart';
|
||||
export 'src/has_many_through.dart';
|
||||
export 'src/has_one.dart';
|
78
packages/relations/lib/src/belongs_to.dart
Normal file
78
packages/relations/lib/src/belongs_to.dart
Normal file
|
@ -0,0 +1,78 @@
|
|||
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:
|
||||
/// * [localKey]: `userId`
|
||||
/// * [foreignKey]: `id`
|
||||
HookedServiceEventListener belongsTo(Pattern servicePath,
|
||||
{String as,
|
||||
String foreignKey,
|
||||
String localKey,
|
||||
getForeignKey(obj),
|
||||
assignForeignObject(foreign, obj)}) {
|
||||
String localId = localKey;
|
||||
var foreignName =
|
||||
as?.isNotEmpty == true ? as : pluralize.singular(servicePath.toString());
|
||||
|
||||
if (localId == null) {
|
||||
localId = foreignName + 'Id';
|
||||
// print('No local key provided for belongsTo, defaulting to \'$localId\'.');
|
||||
}
|
||||
|
||||
return (HookedServiceEvent e) async {
|
||||
var ref = e.service.app.service(servicePath);
|
||||
if (ref == null) throw noService(servicePath);
|
||||
|
||||
_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 == null || indexed is! List || 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);
|
||||
};
|
||||
}
|
78
packages/relations/lib/src/belongs_to_many.dart
Normal file
78
packages/relations/lib/src/belongs_to_many.dart
Normal file
|
@ -0,0 +1,78 @@
|
|||
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"
|
||||
/// multiple members of the service at [servicePath]. Use [as] to set the name
|
||||
/// on the target object.
|
||||
///
|
||||
/// Defaults:
|
||||
/// * [foreignKey]: `userId`
|
||||
/// * [localKey]: `id`
|
||||
HookedServiceEventListener belongsToMany(Pattern servicePath,
|
||||
{String as,
|
||||
String foreignKey,
|
||||
String localKey,
|
||||
getForeignKey(obj),
|
||||
assignForeignObject(List foreign, obj)}) {
|
||||
String localId = localKey;
|
||||
var foreignName =
|
||||
as?.isNotEmpty == true ? as : pluralize.plural(servicePath.toString());
|
||||
|
||||
if (localId == null) {
|
||||
localId = foreignName + 'Id';
|
||||
// print('No local key provided for belongsToMany, defaulting to \'$localId\'.');
|
||||
}
|
||||
|
||||
return (HookedServiceEvent e) async {
|
||||
var ref = e.service.app.service(servicePath);
|
||||
if (ref == null) throw noService(servicePath);
|
||||
|
||||
_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 == null || indexed is! List || indexed.isNotEmpty != true) {
|
||||
await _assignForeignObject(null, obj);
|
||||
} else {
|
||||
var child = indexed is Iterable ? indexed.toList() : [indexed];
|
||||
await _assignForeignObject(child, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.result is Iterable) {
|
||||
await Future.wait(e.result.map(_normalize));
|
||||
} else
|
||||
await _normalize(e.result);
|
||||
};
|
||||
}
|
71
packages/relations/lib/src/has_many.dart
Normal file
71
packages/relations/lib/src/has_many.dart
Normal file
|
@ -0,0 +1,71 @@
|
|||
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);
|
||||
|
||||
_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 == null || indexed is! List || 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);
|
||||
};
|
||||
}
|
100
packages/relations/lib/src/has_many_through.dart
Normal file
100
packages/relations/lib/src/has_many_through.dart
Normal file
|
@ -0,0 +1,100 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'plural.dart' as pluralize;
|
||||
import 'no_service.dart';
|
||||
|
||||
HookedServiceEventListener hasManyThrough(String servicePath, String pivotPath,
|
||||
{String as,
|
||||
String localKey,
|
||||
String pivotKey,
|
||||
String foreignKey,
|
||||
getLocalKey(obj),
|
||||
getPivotKey(obj),
|
||||
getForeignKey(obj),
|
||||
assignForeignObjects(foreign, obj)}) {
|
||||
var foreignName =
|
||||
as?.isNotEmpty == true ? as : pluralize.plural(servicePath.toString());
|
||||
|
||||
return (HookedServiceEvent e) async {
|
||||
var pivotService = e.getService(pivotPath);
|
||||
var foreignService = e.getService(servicePath);
|
||||
|
||||
if (pivotService == null)
|
||||
throw noService(pivotPath);
|
||||
else if (foreignService == null) throw noService(servicePath);
|
||||
|
||||
_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);
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
_getPivotKey(obj) {
|
||||
if (getPivotKey != null)
|
||||
return getPivotKey(obj);
|
||||
else if (obj is Map)
|
||||
return obj[pivotKey ?? 'id'];
|
||||
else if (obj is Extensible)
|
||||
return obj.properties[pivotKey ?? 'id'];
|
||||
else if (pivotKey == null || pivotKey == 'id')
|
||||
return obj.id;
|
||||
else
|
||||
return reflect(obj).getField(new Symbol(pivotKey ?? 'id')).reflectee;
|
||||
}
|
||||
|
||||
_normalize(obj) async {
|
||||
// First, resolve pivot
|
||||
var id = await _getLocalKey(obj);
|
||||
var indexed = await pivotService.index({
|
||||
'query': {pivotKey ?? 'userId': id}
|
||||
});
|
||||
|
||||
if (indexed == null || indexed is! List || indexed.isNotEmpty != true) {
|
||||
await _assignForeignObjects([], obj);
|
||||
} else {
|
||||
// Now, resolve from foreign service
|
||||
var mapped = await Future.wait(indexed.map((pivot) async {
|
||||
var id = await _getPivotKey(obj);
|
||||
var indexed = await foreignService.index({
|
||||
'query': {foreignKey ?? 'postId': id}
|
||||
});
|
||||
|
||||
if (indexed == null ||
|
||||
indexed is! List ||
|
||||
indexed.isNotEmpty != true) {
|
||||
await _assignForeignObjects([], pivot);
|
||||
} else {
|
||||
await _assignForeignObjects(indexed, pivot);
|
||||
}
|
||||
|
||||
return pivot;
|
||||
}));
|
||||
await _assignForeignObjects(mapped, obj);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.result is Iterable) {
|
||||
await Future.wait(e.result.map(_normalize));
|
||||
} else
|
||||
await _normalize(e.result);
|
||||
};
|
||||
}
|
74
packages/relations/lib/src/has_one.dart
Normal file
74
packages/relations/lib/src/has_one.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"
|
||||
/// 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);
|
||||
|
||||
_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 == null || indexed is! List || 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
packages/relations/lib/src/no_service.dart
Normal file
2
packages/relations/lib/src/no_service.dart
Normal file
|
@ -0,0 +1,2 @@
|
|||
ArgumentError noService(Pattern path) =>
|
||||
new ArgumentError("No service exists at path '$path'.");
|
21
packages/relations/lib/src/plural.dart
Normal file
21
packages/relations/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
packages/relations/pubspec.yaml
Normal file
12
packages/relations/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.1
|
||||
environment:
|
||||
sdk: ">=1.19.0"
|
||||
dependencies:
|
||||
angel_framework: ^1.0.0-dev
|
||||
dev_dependencies:
|
||||
angel_seeder: ^1.0.0
|
||||
test: ^0.12.0
|
56
packages/relations/test/belongs_to_test.dart
Normal file
56
packages/relations/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);
|
||||
});
|
||||
}
|
68
packages/relations/test/common.dart
Normal file
68
packages/relations/test/common.dart
Normal file
|
@ -0,0 +1,68 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:json_god/json_god.dart' as god;
|
||||
|
||||
@deprecated
|
||||
class CustomMapService 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
packages/relations/test/has_many_test.dart
Normal file
61
packages/relations/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));
|
||||
});
|
||||
}
|
59
packages/relations/test/has_one_test.dart
Normal file
59
packages/relations/test/has_one_test.dart
Normal file
|
@ -0,0 +1,59 @@
|
|||
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'];
|
||||
print('Author: $author');
|
||||
print('Book: $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