Functionality done

This commit is contained in:
thosakwe 2016-06-23 00:58:21 -04:00
parent 7d54936623
commit 71fe379db9
7 changed files with 111 additions and 45 deletions

View file

@ -1,2 +1,35 @@
# angel_mongo # angel_mongo
![version 1.0.0-dev](https://img.shields.io/badge/version-1.0.0--dev-red.svg)
MongoDB-enabled services for the Angel framework. MongoDB-enabled services for the Angel framework.
# Installation
Add the following to your `pubspec.yaml`:
```yaml
dependencies:
angel_mongo: ^1.0.0-dev
```
# Usage
This library exposes three main classes: `Model`, `MongoService` and `MongoTypedService<T>`.
## Model
`Model` is class with no real functionality; however, it represents a basic MongoDB document, and your services should host inherited classes.
```dart
@Hooked()
class User extends Model {
String username;
String password;
}
```
## MongoService
This class interacts with a `DbCollection` (from mongo_dart) and serializing data to and from Maps.
## MongoTypedService<T>
Does the same as above, but serializes to and from a target class using json_god and its support for reflection.
See the tests for more usage examples.

View file

@ -23,7 +23,6 @@ class Model {
DateTime updatedAt; DateTime updatedAt;
} }
Map _transformId(Map doc) { Map _transformId(Map doc) {
Map result = mergeMap([doc]); Map result = mergeMap([doc]);
result['id'] = doc['_id']; result['id'] = doc['_id'];
@ -33,8 +32,8 @@ Map _transformId(Map doc) {
_lastItem(DbCollection collection, Function _jsonify, [Map params]) async { _lastItem(DbCollection collection, Function _jsonify, [Map params]) async {
return (await (await collection return (await (await collection
.find(where.sortBy('\$natural', descending: true))) .find(where.sortBy('\$natural', descending: true)))
.toList()) .toList())
.map((x) => _jsonify(x, params)) .map((x) => _jsonify(x, params))
.first; .first;
} }
@ -48,8 +47,11 @@ ObjectId _makeId(id) {
} }
Map _removeSensitive(Map data) { Map _removeSensitive(Map data) {
return data..remove('id')..remove('_id')..remove('createdAt')..remove( return data
'updatedAt'); ..remove('id')
..remove('_id')
..remove('createdAt')
..remove('updatedAt');
} }
SelectorBuilder _makeQuery([Map params_]) { SelectorBuilder _makeQuery([Map params_]) {

View file

@ -4,14 +4,7 @@ part of angel_mongo;
class MongoService extends Service { class MongoService extends Service {
DbCollection collection; DbCollection collection;
MongoService(DbCollection this.collection):super(); MongoService(DbCollection this.collection) : super();
Map _transformId(Map doc) {
Map result = mergeMap([doc]);
result['id'] = doc['_id'];
return result..remove('_id');
}
_jsonify(Map doc, [Map params]) { _jsonify(Map doc, [Map params]) {
Map result = {}; Map result = {};
@ -123,5 +116,15 @@ class MongoService extends Service {
} }
} }
@override
Future remove(id, [Map params]) async {
Map result = await read(id, params);
try {
await collection.remove(where.id(_makeId(id)).and(_makeQuery(params)));
return result;
} catch (e, st) {
throw new AngelHttpException(e, stackTrace: st);
}
}
} }

View file

@ -4,19 +4,12 @@ part of angel_mongo;
class MongoTypedService<T> extends Service { class MongoTypedService<T> extends Service {
DbCollection collection; DbCollection collection;
MongoTypedService(DbCollection this.collection):super() { MongoTypedService(DbCollection this.collection) : super() {
if (!reflectType(T).isAssignableTo(reflectType(Model))) if (!reflectType(T).isAssignableTo(reflectType(Model)))
throw new Exception( throw new Exception(
"If you specify a type for MongoService, it must be dynamic, Map, or extend from Model."); "If you specify a type for MongoService, it must be dynamic, Map, or extend from Model.");
} }
Map _transformId(Map doc) {
Map result = mergeMap([doc]);
result['id'] = doc['_id'];
return result..remove('_id');
}
_jsonify(Map doc, [Map params]) { _jsonify(Map doc, [Map params]) {
Map result = {}; Map result = {};
for (var key in doc.keys) { for (var key in doc.keys) {
@ -31,8 +24,7 @@ class MongoTypedService<T> extends Service {
// Clients will always receive JSON. // Clients will always receive JSON.
if ((params != null && params['provider'] != null)) { if ((params != null && params['provider'] != null)) {
return result; return result;
} } else {
else {
// However, when we run server-side, we should return a T, not a Map. // However, when we run server-side, we should return a T, not a Map.
Model typedResult = god.deserializeDatum(result, outputType: T); Model typedResult = god.deserializeDatum(result, outputType: T);
typedResult.createdAt = result['createdAt']; typedResult.createdAt = result['createdAt'];
@ -54,7 +46,7 @@ class MongoTypedService<T> extends Service {
try { try {
Model target = Model target =
(data is T) ? data : god.deserializeDatum(data, outputType: T); (data is T) ? data : god.deserializeDatum(data, outputType: T);
item = god.serializeObject(target); item = god.serializeObject(target);
item = _removeSensitive(item); item = _removeSensitive(item);
@ -86,8 +78,8 @@ class MongoTypedService<T> extends Service {
Future modify(id, Map data, [Map params]) async { Future modify(id, Map data, [Map params]) async {
ObjectId _id = _makeId(id); ObjectId _id = _makeId(id);
try { try {
Map result = await collection.findOne( Map result =
where.id(_id).and(_makeQuery(params))); await collection.findOne(where.id(_id).and(_makeQuery(params)));
if (result == null) { if (result == null) {
throw new AngelHttpException.NotFound( throw new AngelHttpException.NotFound(
@ -108,8 +100,8 @@ class MongoTypedService<T> extends Service {
@override @override
Future update(id, _data, [Map params]) async { Future update(id, _data, [Map params]) async {
try { try {
Model data = (_data is T) ? _data : god.deserializeDatum( Model data =
_data, outputType: T); (_data is T) ? _data : god.deserializeDatum(_data, outputType: T);
ObjectId _id = _makeId(id); ObjectId _id = _makeId(id);
Map rawData = _removeSensitive(god.serializeObject(data)); Map rawData = _removeSensitive(god.serializeObject(data));
rawData['_id'] = _id; rawData['_id'] = _id;
@ -128,4 +120,16 @@ class MongoTypedService<T> extends Service {
throw new AngelHttpException(e, stackTrace: st); throw new AngelHttpException(e, stackTrace: st);
} }
} }
@override
Future remove(id, [Map params]) async {
Map result = await read(id, params);
try {
await collection.remove(where.id(_makeId(id)).and(_makeQuery(params)));
return result;
} catch (e, st) {
throw new AngelHttpException(e, stackTrace: st);
}
}
} }

View file

@ -23,6 +23,9 @@ wireHooked(HookedService hooked) {
}) })
..afterUpdated.listen((HookedServiceEvent event) { ..afterUpdated.listen((HookedServiceEvent event) {
print("Just updated: ${event.result}"); print("Just updated: ${event.result}");
})
..afterRemoved.listen((HookedServiceEvent event) {
print("Just removed: ${event.result}");
}); });
} }
@ -48,7 +51,7 @@ main() {
app.use('/api', Greetings); app.use('/api', Greetings);
HttpServer server = HttpServer server =
await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
url = "http://${server.address.host}:${server.port}"; url = "http://${server.address.host}:${server.port}";
}); });
@ -93,9 +96,8 @@ main() {
expect(response.statusCode, equals(HttpStatus.OK)); expect(response.statusCode, equals(HttpStatus.OK));
Map created = god.deserialize(response.body); Map created = god.deserialize(response.body);
response = await client.patch( response = await client.patch("$url/api/${created['id']}",
"$url/api/${created['id']}", body: god.serialize({"to": "Mom"}), body: god.serialize({"to": "Mom"}), headers: headers);
headers: headers);
Map modified = god.deserialize(response.body); Map modified = god.deserialize(response.body);
expect(response.statusCode, equals(HttpStatus.OK)); expect(response.statusCode, equals(HttpStatus.OK));
expect(modified['id'], equals(created['id'])); expect(modified['id'], equals(created['id']));
@ -109,9 +111,8 @@ main() {
expect(response.statusCode, equals(HttpStatus.OK)); expect(response.statusCode, equals(HttpStatus.OK));
Map created = god.deserialize(response.body); Map created = god.deserialize(response.body);
response = await client.post( response = await client.post("$url/api/${created['id']}",
"$url/api/${created['id']}", body: god.serialize({"to": "Updated"}), body: god.serialize({"to": "Updated"}), headers: headers);
headers: headers);
Map modified = god.deserialize(response.body); Map modified = god.deserialize(response.body);
expect(response.statusCode, equals(HttpStatus.OK)); expect(response.statusCode, equals(HttpStatus.OK));
expect(modified['id'], equals(created['id'])); expect(modified['id'], equals(created['id']));
@ -119,11 +120,19 @@ main() {
expect(modified['updatedAt'], isNot(null)); expect(modified['updatedAt'], isNot(null));
}); });
test('remove item', () async {}); test('remove item', () async {
var response = await client.post("$url/api",
body: god.serialize(testGreeting), headers: headers);
Map created = god.deserialize(response.body);
test(r'$sort', () async { int lastCount = (await Greetings.index()).length;
await client.delete("$url/api/${created['id']}");
expect((await Greetings.index()).length, equals(lastCount - 1));
}); });
test(r'$sort', () async {});
test('query parameters', () async {}); test('query parameters', () async {});
}); });
} }

1
test/packages Symbolic link
View file

@ -0,0 +1 @@
../packages

View file

@ -54,7 +54,7 @@ main() {
app.use('/api', Greetings); app.use('/api', Greetings);
HttpServer server = HttpServer server =
await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0);
url = "http://${server.address.host}:${server.port}"; url = "http://${server.address.host}:${server.port}";
}); });
@ -109,9 +109,8 @@ main() {
expect(response.statusCode, equals(HttpStatus.OK)); expect(response.statusCode, equals(HttpStatus.OK));
Map created = god.deserialize(response.body); Map created = god.deserialize(response.body);
response = await client.patch( response = await client.patch("$url/api/${created['id']}",
"$url/api/${created['id']}", body: god.serialize({"to": "Mom"}), body: god.serialize({"to": "Mom"}), headers: headers);
headers: headers);
Map modified = god.deserialize(response.body); Map modified = god.deserialize(response.body);
expect(response.statusCode, equals(HttpStatus.OK)); expect(response.statusCode, equals(HttpStatus.OK));
expect(modified['id'], equals(created['id'])); expect(modified['id'], equals(created['id']));
@ -129,9 +128,8 @@ main() {
expect(response.statusCode, equals(HttpStatus.OK)); expect(response.statusCode, equals(HttpStatus.OK));
Map created = god.deserialize(response.body); Map created = god.deserialize(response.body);
response = await client.post( response = await client.post("$url/api/${created['id']}",
"$url/api/${created['id']}", body: god.serialize({"to": "Updated"}), body: god.serialize({"to": "Updated"}), headers: headers);
headers: headers);
Map modified = god.deserialize(response.body); Map modified = god.deserialize(response.body);
expect(response.statusCode, equals(HttpStatus.OK)); expect(response.statusCode, equals(HttpStatus.OK));
expect(modified['id'], equals(created['id'])); expect(modified['id'], equals(created['id']));
@ -139,7 +137,23 @@ main() {
expect(modified['updatedAt'], isNot(null)); expect(modified['updatedAt'], isNot(null));
}); });
test('remove item', () async {}); test('remove item', () async {
var response = await client.post("$url/api",
body: god.serialize(testGreeting), headers: headers);
Map created = god.deserialize(response.body);
int lastCount = (await Greetings.index()).length;
await client.delete("$url/api/${created['id']}");
expect((await Greetings.index()).length, equals(lastCount - 1));
Greeting bernie =
await Greetings.create(new Greeting(to: "Bernie Sanders"));
lastCount = (await Greetings.index()).length;
await Greetings.remove(bernie.id);
expect((await Greetings.index()).length, equals(lastCount - 1));
});
test(r'$sort', () async {}); test(r'$sort', () async {});