Composing `Future` result in Play Framework with Scala
monads, playframework, scala
Solution
It's that double-nesting (`Future` of `Option`) that seems to get people every time. Things become a lot easier if you can flatten stuff out first.
In this case, `Future` already has a way of representing an error condition, it can wrap an `Exception` as well as a success value, that's something you can use...
// making this a Singleton avoids the cost of building a stack trace,
// which only happens when an Exception is constructed (not when it's thrown)
object NotFoundException extends RuntimeException("Empty Option")
// The map operation will trap any thrown exception and fail the Future
def squish[T](x: Future[Option[T]]) =
x map { _.getOrElse(throw NotFoundException) }
It's now a lot easier to use those squished results in a comprehension:
val result = for {
user <- squish(Users findById userId)
itemId <- squish(Items.create(user, ...))
} yield {
Ok(itemId)
} recover {
case NotFoundException => NotFound
}
Which will, of course, evaluate to a Future. This is async programming, after all :)
Any exceptions other than `NotFoundException` will still be exposed.
Problem
I am trying to write a Play Framework asynchronous Action for the following URL: ``` POST /users/:userId/items ``` My database calls all return `Future[...]`, where `...` is `Option[A]` for `find` methods and `Option[Id]` for create methods. I would like to check for the existence of the userId before trying to create the new item. I have a method `Users.findById(userId)` that returns a `Future[Option[User]]`. The result is `Some(User)` if the user exists and `None` if not. `Items.create()` also returns a `Future[Option[itemId]]`. I am trying to compose something using `for`: ``` for { user <- Users.findById(userId) if user.isDefined } yield { Items.create(...) map { itemId => Ok(itemId) } getOrElse NotFound } ``` I would like to return `Ok(itemId)` if the item is successfully created. I'm not sure how to handle the error case. I would like to return `NotFound` if either the userId is invalid or the item cannot be created (maybe a field conflicts with a unique value already in the database). I'm not sure what to put after the `for` structure. I tried `getOrElse`, but that does not compile, since `Future` does not have a `getOrElse` method. Ideally, I can handle URLs containing several ids to check, e.g.: ``` PUT /users/:userId/foo/:fooId/bar/:barId ``` and confirm that `userId`, `fooId`, and `barId` are all valid before doing the update. All of those calls (`Users.findById`, `Foo.findById`, and `Bar.findById`) will return `Future[Option[A]]`.