Skip to content

Instantly share code, notes, and snippets.

@emilniklas
Last active March 4, 2016 18:14
Show Gist options
  • Save emilniklas/04ba843f2cbebf508845 to your computer and use it in GitHub Desktop.
Save emilniklas/04ba843f2cbebf508845 to your computer and use it in GitHub Desktop.
dependencies:
embla: ^0.1.13
embla_trestle: ^0.1.0
import 'dart:async';
import 'package:embla/http.dart';
import 'package:embla/http_annotations.dart' as http;
import 'package:embla/http_basic_middleware.dart';
import 'package:embla_trestle/embla_trestle.dart';
export 'package:embla/bootstrap.dart';
get embla => [
// As a default, the DatabaseBootstrapper connects to an in-memory database.
// This is configurable in this constructor.
new DatabaseBootstrapper(),
new HttpBootstrapper(
pipeline: pipe(
// Since we will be receiving POST and PUT requests, we must parse the
// request body first. This middleware will make it possible to inject
// an Input object in the handlers.
InputParserMiddleware,
// This routes the entire PostsController to the /posts namespace.
Route.all('posts/*', PostsController)
)
)
];
/// The [Model] superclass is provided by Trestle, whose API is exposed
/// via the embla_trestle import.
class Post extends Model {
@field String title;
@field String body;
/// For convenience
void apply(Map<String, dynamic> map) {
title = map['title'] ?? title;
body = map['body'] ?? body;
}
}
class PostsController extends Controller {
final Repository<Post> posts;
/// We use Dependency Injection to inject the Trestle Repository
PostsController(this.posts);
/// A method with the name [index] is automatically bound to the
/// root of the current routing namespace. So the annotation below
/// is equivalent to
///
/// @http.Get('/')
/// // or
/// @http.Get('')
@http.Get() index() {
return posts.all();
}
/// If we type annotate the wildcard parameter with [int] it will
/// only be provided if the wildcard is numeric (^\d+$). Else it
/// be set to null. The [_findById] method helps with this case.
@http.Get(':id') show({int id}) {
return _findById(id);
}
/// Thanks to the [InputParserMiddleware] we can inject the [Input]
/// object, whose [body] property will contain the body of the request.
///
/// We use that to create a new Post, and save it to the database.
@http.Post('') store(Input input) async {
final post = new Post()
..apply(input.body);
await posts.save(post);
return post;
}
/// Update is similar to store, but instead of creating a new
/// [Post], we find the one with the provided id and update it.
@http.Put(':id') update(Input input, {int id}) async {
final post = (await _findById(id))
..apply(input.body);
await posts.save(post);
return post;
}
/// Deleting is trivial.
@http.Delete(':id') delete({int id}) async {
final post = await _findById(id);
await posts.delete(post);
return { 'message': 'OK' };
}
/// This method is a convenience method to get the [Post] with the
/// provided [id] out of the database.
Future<Post> _findById(int id) async {
// If the id is null, that means the wildcard did not match ^\d+$,
// so we can return a 400 message (Bad Request).
if (id == null) {
abortBadRequest({ 'message': 'The id must be numeric' });
}
// The find method below will get the first Post it can find with
// the id. If it find none it will throw a StateError, so we'll
// have to catch that.
try {
return await posts.find(id);
} on StateError {
abortNotFound({ 'message': 'No post has an id of $id' });
}
}
}
import 'dart:async';
import 'package:embla/http.dart';
import 'package:embla/http_annotations.dart' as http;
import 'package:embla/http_basic_middleware.dart';
import 'package:embla_trestle/embla_trestle.dart';
export 'package:embla/bootstrap.dart';
get embla => [
new DatabaseBootstrapper(),
new HttpBootstrapper(
pipeline: pipe(
InputParserMiddleware,
Route.all('posts/*', PostsController)
)
)
];
class Post extends Model {
@field String title;
@field String body;
void apply(Map<String, dynamic> map) {
title = map['title'] ?? title;
body = map['body'] ?? body;
}
}
class PostsController extends Controller {
final Repository<Post> posts;
PostsController(this.posts);
@http.Get() index() {
return posts.all();
}
@http.Get(':id') show({int id}) {
return _findById(id);
}
@http.Post('') store(Input input) async {
final post = new Post()
..apply(input.body);
await posts.save(post);
return post;
}
@http.Put(':id') update(Input input, {int id}) async {
final post = (await _findById(id))
..apply(input.body);
await posts.save(post);
return post;
}
@http.Delete(':id') delete({int id}) async {
final post = await _findById(id);
await posts.delete(post);
return { 'message': 'OK' };
}
Future<Post> _findById(int id) async {
if (id == null) {
abortBadRequest({ 'message': 'The id must be numeric' });
}
try {
return await posts.find(id);
} on StateError {
abortNotFound({ 'message': 'No post has an id of $id' });
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment