Update a row only if condition is met - scala

I want to update a certain column in row only if row has a valid data. Being specific: I have a table with Event which stores start, stop and isActive flag.
I would like some Events activate by setting isActive to true, however I need to check if start and stop dates are valid.
model:
case class Event {start:DateTime, stop:DateTime, isActive:Boolean}
my validation method signature :
validateEvent(ev: Event): Boolean
My first approach:
def activateEv() = Action.async(parse.json) {
request => {
...
val ev = db.run(dao.findEvById(poid, uid))
val ret = ev.flatMap {
case st: Option[Event] => if (validateEvent(st.get)) {
db.run(dao.updateActivity(poid, true).map {
case 0 => false
case other => true
}
} else Future(false)
}
...
}
}
I believe that it is not the way how this problem should be addressed.
Could you advice ?
Maybe only one db.run will be sufficient ?

This can be achieved in a single db.run using combinators (e.g. flatMap) on DBIOAction objects. Assuming that your dao methods look like that:
case object dao {
def findEvById(poid: Int, uid: Int): DBIOAction[Option[Event], NoStream, Effect.Read] = ???
// In your case `updateActivity` returns an `Int` and you're mapping it to a `Boolean`.
// That mapping could be placed here, so `updateActivity` would return `Boolean` now.
def updateActivity(poid: Int, bool: Boolean): DBIOAction[Boolean, NoStream, Effect.Write] = ???
}
This is how we can achieve the thing you're looking for:
...
val action = dao.findEvById(poid, uid).flatMap {
case Some(event) if validateEvent(event) => dao.updateActivity(poid, true)
case _ => DBIO.successful(false)
}.transactionally
db.run(action)
...
As you see we have a transaction here, which would make a selection followed by an update (only if the event is valid). Moreover, that whole select then update action could be a separate method in your dao.

Related

Scala Futures for-comprehension with a list of values

I need to execute a Future method on some elements I have in a list simultaneously. My current implementation works sequentially, which is not optimal for saving time. I did this by mapping my list and calling the method on each element and processing the data this way.
My manager shared a link with me showing how to execute Futures simultaneously using for-comprehension but I cannot see/understand how I can implement this with my List.
The link he shared with me is https://alvinalexander.com/scala/how-use-multiple-scala-futures-in-for-comprehension-loop/
Here is my current code:
private def method1(id: String): Tuple2[Boolean, List[MyObject]] = {
val workers = List.concat(idleWorkers, activeWorkers.keys.toList)
var ready = true;
val workerStatus = workers.map{ worker =>
val option = Await.result(method2(worker), 1 seconds)
var status = if (option.isDefined) {
if (option.get._2 == id) {
option.get._1.toString
} else {
"INVALID"
}
} else "FAILED"
val status = s"$worker: $status"
if (option.get._1) {
ready = false
}
MyObject(worker.toString, status)
}.toList.filterNot(s => s. status.contains("INVALID"))
(ready, workerStatus)
}
private def method2(worker: ActorRef): Future[Option[(Boolean, String)]] = Future{
implicit val timeout: Timeout = 1 seconds;
Try(Await.result(worker ? GetStatus, 1 seconds)) match {
case Success(extractedVal) => extractedVal match {
case res: (Boolean, String) => Some(res)
case _ => None
}
case Failure(_) => { None }
case _ => { None }
}
}
If someone could suggest how to implement for-comprehension in this scenario, I would be grateful. Thanks
For method2 there is no need for the Future/Await mix. Just map the Future:
def method2(worker: ActorRef): Future[Option[(Boolean, String)]] =
(worker ? GetStatus).map{
case res: (Boolean, String) => Some(res)
case _ => None
}
For method1 you likewise need to map the result of method2 and do the processing inside the map. This will make workerStatus a List[Future[MyObject]] and means that everything runs in parallel.
Then use Future.sequence(workerStatus) to turn the List[Future[MyObject]] into a Future[List[MyObject]]. You can then use map again to do the filtering/ checking on that List[MyObject]. This will happen when all the individual Futures have completed.
Ideally you would then return a Future from method1 to keep everything asynchronous. You could, if absolutely necessary, use Await.result at this point which would wait for all the asynchronous operations to complete (or fail).

Scala Action Map Implementation Issue (follow up)

This is a fairly long winded question and a follow up to my last one.
I have the following code for an application being built - I am looking to call the function in handleOne but it is not working in the action map. I think this is due to the unit assigned to statesVotes in the handler. The goal is to create a menu driven application that performs a set of desired functions. The function in question here is: Get all the state values and display suitably formatted.
Potentially have to make the states into a map but looking for the same functionality of the case class.
import scala.io.StdIn.readInt
object myApp3 extends App{
val dataRE = "([^(]+) \\((\\d+)\\),(.+)".r
val pVotes = "([^:]+):(\\d+)".r
case class State(name : String
,code : Int
,parties : Array[(String,Int)])
val states: List[State] =
util.Using(io.Source.fromFile("filename.txt"))(_.getLines().toList)
.get //will throw if read file fails
.collect{case dataRE(name,code,votes) =>
State(name.trim
,code.toInt
,votes.split(",")
.collect{case pVotes(p,v) => (p,v.toInt)}
)
}
val actionMap = Map[Int, () => Boolean](1 -> handleOne)
var opt = 0
do{
opt = readOption
} while (menu(opt))
def readOption: Int = {
println(
"""|Please select one of the following:
| 1 - Show All States and Votes
| 2 - CW Option 2
| 3 - quit""".stripMargin)
readInt()
}
def menu(option: Int): Boolean = {
actionMap.get(option) match {
case Some(f) => f()
case None =>
println("Command not recognized!")
true
}
}
// handle one calls function mnuShowStatesVotes, which invokes function statesVotes
def handleOne(): Boolean = {
mnuShowStatesVotes(statesVotes : List[State])
true
}
def mnuShowStatesVotes(f:() => List[State]) = {
f() foreach(println())
}
def statesVotes = states.sortBy(_.name) //alphabetical order of states
.foreach{ st =>
println(st.name) //show line by split by state name
st.parties
.sortBy(-_._2) //sorts parties by votes in descending order
.map{case (p,v) => f"\t$p%-12s:$v%9d"}
.foreach(println)
}
}
Essentially want the menu option handleOne to correctly invoke the function in statesVotes.
The text file being used can be found below:
Alabama (9),Democratic:849624,Republican:1441170,Libertarian:25176,Others:7312
Alaska (3),Democratic:153778,Republican:189951,Libertarian:8897,Others:6904
Arizona (11),Democratic:1672143,Republican:1661686,Libertarian:51465,Green:1557,Others:475
It seems to me that your code would benefit by adopting a clear and distinct separation/segregation of roles and responsibilities.
Let's get the preliminaries taken care of.
import scala.util.{Try, Success, Failure, Using}
case class State(name : String
,code : Int
,parties : Array[(String,Int)])
Now let's parse the input data.
This code has one job to do: load the data from the input file. It takes one parameter, the input filename, and returns either Success() with the accumulated data, or Failure() with the error exception.
def readFile(filename: String): Try[List[State]] = {
val dataRE = "([^(]+) \\((\\d+)\\),(.+)".r
val pVotes = "([^:]+):(\\d+)".r
Using(io.Source.fromFile(filename)) {
_.getLines()
.toList
.collect{ case dataRE(name, code, votes) =>
State(name.trim
,code.toInt
,votes.split(",")
.collect{case pVotes(p,v) => (p,v.toInt)})
}
}
}
Note that collect() will simply ignore file data the doesn't fit the expected format. If you were to use map() instead then bad input data would cause a Failure().
Now let's put all the output methods, and their descriptions, under one roof. This is most of what the user will see.
class Menu(states: List[State]) {
def apply(key: String): Boolean = {
val (_, op, continue) = lookup(key)
op()
continue
}
private val lookup: Map[String,(String,()=>Unit,Boolean)] =
Map("?" -> ("show this menu", menu _, true)
,"menu" -> ("show this menu", menu _, true)
,"all" -> ("display all voting data", all _, true)
,"st" -> ("vote totals by state", stVotes _, true)
,"x" -> ("exit", done _, false)
,"quit" -> ("exit", done _, false)
).withDefaultValue(("",unknown _, true))
private def done(): Unit = println("bye")
private def unknown(): Unit =
println("unknown selection ('?' for main menu)")
private def menu(): Unit =
lookup.keys.toVector.sorted
.map(k => s"$k\t: ${lookup(k)._1}")
.foreach(println)
private def all(): Unit =
states.sortBy(_.name) //alphabetical
.foreach{ st =>
println(st.name) //state name
st.parties
.sortBy(-_._2) //votes in decreasing order
.map{case (p,v) => f"\t$p%-12s:$v%9d"}
.foreach(println)
}
private def stVotes(): Unit =
states.map(st => (st.name, st.parties.map(_._2).sum))
.sortBy(-_._2) //votes in decreasing order
.map{case (state,total) => f"$state%-9s:$total%8d"}
.foreach(println)
}
Notice that only the apply() method is public. Everything else is private and under wraps.
To create a new data report you just add an entry in the lookup Map and add the new method to produce the output.
Now all we need is the code to tie the pieces together and to take user input.
def main(args: Array[String]): Unit =
args.headOption.map(readFile) match {
case None =>
println(s"usage: ${this.productPrefix} <data_file>")
case Some(Failure(exc)) =>
println(s"Error reading data file: $exc")
case Some(Success(stateData)) =>
val menu = new Menu(stateData)
menu("menu")
Iterator.continually(menu(io.StdIn.readLine(">> ").toLowerCase))
.dropWhile(identity)
.next()
}
Note that this.productPrefix is made available if the surrounding object is a case object.

MVar tryPut returns true and isEmpty also returns true

I wrote simple callback(handler) function which i pass to async api and i want to wait for result:
object Handlers {
val logger: Logger = Logger("Handlers")
implicit val cs: ContextShift[IO] =
IO.contextShift(ExecutionContext.Implicits.global)
class DefaultHandler[A] {
val response: IO[MVar[IO, A]] = MVar.empty[IO, A]
def onResult(obj: Any): Unit = {
obj match {
case obj: A =>
println(response.flatMap(_.tryPut(obj)).unsafeRunSync())
println(response.flatMap(_.isEmpty).unsafeRunSync())
case _ => logger.error("Wrong expected type")
}
}
def getResponse: A = {
response.flatMap(_.take).unsafeRunSync()
}
}
But for some reason both tryPut and isEmpty(when i'd manually call onResult method) returns true, therefore when i calling getResponse it sleeps forever.
This is the my test:
class HandlersTest extends FunSuite {
test("DefaultHandler.test") {
val handler = new DefaultHandler[Int]
handler.onResult(3)
val response = handler.getResponse
assert(response != 0)
}
}
Can somebody explain why tryPut returns true, but nothing puts. And what is the right way to use Mvar/channels in scala?
IO[X] means that you have the recipe to create some X. So on your example, yuo are putting in one MVar and then asking in another.
Here is how I would do it.
object Handlers {
trait DefaultHandler[A] {
def onResult(obj: Any): IO[Unit]
def getResponse: IO[A]
}
object DefaultHandler {
def apply[A : ClassTag]: IO[DefaultHandler[A]] =
MVar.empty[IO, A].map { response =>
new DefaultHandler[A] {
override def onResult(obj: Any): IO[Unit] = obj match {
case obj: A =>
for {
r1 <- response.tryPut(obj)
_ <- IO(println(r1))
r2 <- response.isEmpty
_ <- IO(println(r2))
} yield ()
case _ =>
IO(logger.error("Wrong expected type"))
}
override def getResponse: IO[A] =
response.take
}
}
}
}
The "unsafe" is sort of a hint, but every time you call unsafeRunSync, you should basically think of it as an entire new universe. Before you make the call, you can only describe instructions for what will happen, you can't actually change anything. During the call is when all the changes occur. Once the call completes, that universe is destroyed, and you can read the result but no longer change anything. What happens in one unsafeRunSync universe doesn't affect another.
You need to call it exactly once in your test code. That means your test code needs to look something like:
val test = for {
handler <- TestHandler.DefaultHandler[Int]
_ <- handler.onResult(3)
response <- handler.getResponse
} yield response
assert test.unsafeRunSync() == 3
Note this doesn't really buy you much over just using the MVar directly. I think you're trying to mix side effects inside IO and outside it, but that doesn't work. All the side effects need to be inside.

How to wait multiple messages properly and become new state after that

I have an actor, that should receive two messages and after that, become new initialised state. I wrote some code, but it seems very ugly:
def waitInitialisation(#Nullable one: Integer, #Nullable two: String): Receive = {
case _one: Int =>
if (two == null)
context.become(waitInitialisation(_one, two))
else {
doSomething()
context.become(initialised(_one, two))
}
case _two: String =>
if (one == null)
context.become(waitInitialisation(one, _two))
else {
doSomething()
context.become(initialised(one, _two))
}
}
def initialised(one: Int, two: String): Receive = ???
override def receive: Receive = waitInitialisation(null, null)
So problems, what I see: null checking and duplicate code. How I can simplify my implementation and make it properly?
#chunjef already gave a great direction in using Options which is the way to go in Scala. I'm letting below two other options in which I use pattern matching to make the code a bit more beautiful.
Before you take a look at the two solutions please bear in mind that usually calling .get on an Option is not recommended and will possibly get you some compiler warnings. Anyways, we're always sure to make the right call in our examples because we're checking beforehand if the option isDefined.
Oh, and when working with values that might come null - like operating with Java APIs - always use Option's apply, not Some's apply.
The first one defines essentially the same method but structured a bit differently:
def waitInit(one: Option[Int], two: Option[String]): Receive = {
case value: Int if two.isDefined =>
context.become(initialised(value, two.get))
case value: Int =>
context.become(waitInit(Option(value), two))
case value: String if one.isDefined =>
context.become(initialised(one.get, value))
case value: String =>
context.become(waitInit(one, Option(value)))
}
override val receive = waitInit(None, None)
The second one splits this logic in two pieces so you can follow on it easier:
def waitOne(two: Option[String]): Receive = {
case one: Int if two.isDefined =>
context.become(initialised(one, two.get))
case one: Int =>
context.become(waitOne(two) orElse waitTwo(Option(one)))
}
def waitTwo(one: Option[Int]): Receive = {
case two: String if one.isDefined =>
context.become(initialised(one.get, two))
case two: String =>
context.become(waitOne(Option(two)) orElse waitTwo(one))
}
override val receive: Receive =
waitOne(None) orElse waitTwo(None)
That's it, I didn't put some code here (like the definition of initialised) essentially because it's the same.
Enjoy :)
Using Option is the idiomatic way in Scala to handle nulls:
def waitInitialisation(one: Option[Int], two: Option[String]): Receive = {
case _one: Int =>
two match {
case Some(s) =>
doSomething()
context.become(initialised(_one, s))
case None =>
context.become(waitInitialisation(Option(_one), None))
}
case _two: String =>
one match {
case Some(i) =>
doSomething()
context.become(initialised(i, _two))
case None =>
context.become(waitInitialisation(None, Option(_two)))
}
}
def initialised(one: Int, two: String): Receive = ???
def receive = waitInitialisation(None, None)
As for "code duplication," I wouldn't get hung up on the number of become calls. Your actor can be in one of the four following states:
waitInitialisation(None, None)
waitInitialisation(Some, None)
waitInitialisation(None, Some)
initialised
You probably could implement the state changes with the FSM trait, but that would be overkill for your case. The way you've structured your actor is simple and clear.
You can also do that with an additional message.
With that solution, adding or changing messages for the init will be easy :
case class InitState(
one : Option[Int],
two : Option[String],
three : Option[Boolean]
)
{
def fire() : Unit = {
context.become(waitInit(this))
self ! this
}
}
def waitInit(st : InitState = InitState(None, None, None)) : Receive = {
case i : Int =>
st.copy(one = Some(i)).fire()
case s : String =>
st.copy( two = Some(s)).fire()
case b : Boolean =>
st.copy(three = Some(b)).fire()
case InitState(Some(i : Int), Some(s : String), Some(b : Boolean)) =>
context.become(afterInit(i, s, b))
case _ : InitState =>
}
def afterInit(one : Int, two : String, three : Boolean) : Receive = ???
def receive = waitInit()
As chunjef wrote, for not so simple cases, the good choice will be using FSM.
akka-contrib provided this Aggregator pattern that is similar to what you are looking for: https://github.com/akka/akka/blob/master/akka-contrib/src/main/scala/akka/contrib/pattern/Aggregator.scala
This code is now deprecated but you can copy it into your project.
Here you can find how it works (this documentation is pretty old): http://doc.akka.io/docs/akka/2.3.0/contrib/aggregator.html
The main idea is to use expect or expectOnce to receive certain messages. Once that has happened, you can do whatever else.

Scala-way to handle conditions in for-comprehensions?

I am trying to create a neat construction with for-comprehension for business logic built on futures. Here is a sample which contains a working example based on Exception handling:
(for {
// find the user by id, findUser(id) returns Future[Option[User]]
userOpt <- userDao.findUser(userId)
_ = if (!userOpt.isDefined) throw new EntityNotFoundException(classOf[User], userId)
user = userOpt.get
// authenticate it, authenticate(user) returns Future[AuthResult]
authResult <- userDao.authenticate(user)
_ = if (!authResult.ok) throw new AuthFailedException(userId)
// find the good owned by the user, findGood(id) returns Future[Option[Good]]
goodOpt <- goodDao.findGood(goodId)
_ = if (!good.isDefined) throw new EntityNotFoundException(classOf[Good], goodId)
good = goodOpt.get
// check ownership for the user, checkOwnership(user, good) returns Future[Boolean]
ownership <- goodDao.checkOwnership(user, good)
if (!ownership) throw new OwnershipException(user, good)
_ <- goodDao.remove(good)
} yield {
renderJson(Map(
"success" -> true
))
})
.recover {
case ex: EntityNotFoundException =>
/// ... handle error cases ...
renderJson(Map(
"success" -> false,
"error" -> "Your blahblahblah was not found in our database"
))
case ex: AuthFailedException =>
/// ... handle error cases ...
case ex: OwnershipException =>
/// ... handle error cases ...
}
However this might be seen as a non-functional or non-Scala way to handle the things. Is there a better way to do this?
Note that these errors come from different sources - some are at the business level ('checking ownership') and some are at controller level ('authorization') and some are at db level ('entity not found'). So approaches when you derive them from a single common error type might not work.
Don't use exceptions for expected behaviour.
It's not nice in Java, and it's really not nice in Scala. Please see this question for more information about why you should avoid using exceptions for regular control flow. Scala is very well equipped to avoid using exceptions: you can use Eithers.
The trick is to define some failures you might encounter, and convert your Options into Eithers that wrap these failures.
// Failures.scala
object Failures {
sealed trait Failure
// Four types of possible failures here
case object UserNotFound extends Failure
case object NotAuthenticated extends Failure
case object GoodNotFound extends Failure
case object NoOwnership extends Failure
// Put other errors here...
// Converts options into Eithers for you
implicit class opt2either[A](opt: Option[A]) {
def withFailure(f: Failure) = opt.fold(Left(f))(a => Right(a))
}
}
Using these helpers, you can make your for comprehension readable and exception free:
import Failures._
// Helper function to make ownership checking more readable in the for comprehension
def checkGood(user: User, good: Good) = {
if(checkOwnership(user, good))
Right(good)
else
Left(NoOwnership)
}
// First create the JSON
val resultFuture: Future[Either[Failure, JsResult]] = for {
userRes <- userDao.findUser(userId)
user <- userRes.withFailure(UserNotFound).right
authRes <- userDao.authenticate(user)
auth <- authRes.withFailure(NotAuthenticated).right
goodRes <- goodDao.findGood(goodId)
good <- goodRes.withFailure(GoodNotFound).right
checkedGood <- checkGood(user, good).right
} yield renderJson(Map("success" -> true)))
// Check result and handle any failures
resultFuture.map { result =>
result match {
case Right(json) => json // serve json
case Left(failure) => failure match {
case UserNotFound => // Handle errors
case NotAuthenticated =>
case GoodNotFound =>
case NoOwnership =>
case _ =>
}
}
}
You could clean up the for comprehension a little to look like this:
for {
user <- findUser(userId)
authResult <- authUser(user)
good <- findGood(goodId)
_ <- checkOwnership(user, good)
_ <- goodDao.remove(good)
} yield {
renderJson(Map(
"success" -> true
))
}
Assuming these methods:
def findUser(id:Long) = find(id, userDao.findUser)
def findGood(id:Long) = find(id, goodDao.findGood)
def find[T:ClassTag](id:Long, f:Long => Future[Option[T]]) = {
f(id).flatMap{
case None => Future.failed(new EntityNotFoundException(implicitly[ClassTag[T]].runtimeClass, id))
case Some(entity) => Future.successful(entity)
}
}
def authUser(user:User) = {
userDao.authenticate(user).flatMap{
case result if result.ok => Future.failed(new AuthFailedException(userId))
case result => Future.successful(result)
}
}
def checkOwnership(user:User, good:Good):Future[Boolean] = {
val someCondition = true //real logic for ownership check goes here
if (someCondition) Future.successful(true)
else Future.failed(new OwnershipException(user, good))
}
The idea here is to use flatMap to turn things like Options that are returned wrapped in Futures into failed Futures when they are None. There are going to be a lot of ways to do clean up that for comp and this is one possible way to do it.
The central challenge is that for-comprehensions can only work on one monad at a time, in this case it being the Future monad and the only way to short-circuit a sequence of future calls is for the future to fail. This works because the subsequent calls in the for-comprehension are just map and flatmap calls, and the behavior of a map/flatmap on a failed Future is to return that future and not execute the provided body (i.e. the function being called).
What you are trying to achieve is the short-cicuiting of a workflow based on some conditions and not do it by failing the future. This can be done by wrapping the result in another container, let's call it Result[A], which gives the comprehension a type of Future[Result[A]]. Result would either contain a result value, or be a terminating result. The challenge is how to:
provide subsequent function calls the value contained by a prior non-terminating Result
prevent the subsequent function call from being evaluated if the Result is terminating
map/flatmap seem like the candidates for doing these types of compositions, except we will have to call them manually, since the only map/flatmap that the for-comprehension can evaluate is one that results in a Future[Result[A]].
Result could be defined as:
trait Result[+A] {
// the intermediate Result
def value: A
// convert this result into a final result based on another result
def given[B](other: Result[B]): Result[A] = other match {
case x: Terminator => x
case v => this
}
// replace the value of this result with the provided one
def apply[B](v: B): Result[B]
// replace the current result with one based on function call
def flatMap[A2 >: A, B](f: A2 => Future[Result[B]]): Future[Result[B]]
// create a new result using the value of both
def combine[B](other: Result[B]): Result[(A, B)] = other match {
case x: Terminator => x
case b => Successful((value, b.value))
}
}
For each call, the action is really a potential action, as calling it on or with a terminating result, will simply maintain the terminating result. Note that Terminator is a Result[Nothing] since it will never contain a value and any Result[+A] can be a Result[Nothing].
The terminating result is defined as:
sealed trait Terminator extends Result[Nothing] {
val value = throw new IllegalStateException()
// The terminator will always short-circuit and return itself as
// the success rather than execute the provided block, thus
// propagating the terminating result
def flatMap[A2 >: Nothing, B](f: A2 => Future[Result[B]]): Future[Result[B]] =
Future.successful(this)
// if we apply just a value to a Terminator the result is always the Terminator
def apply[B](v: B): Result[B] = this
// this apply is a convenience function for returning this terminator
// or a successful value if the input has some value
def apply[A](opt: Option[A]) = opt match {
case None => this
case Some(v) => Successful[A](v)
}
// this apply is a convenience function for returning this terminator or
// a UnitResult
def apply(bool: Boolean): Result[Unit] = if (bool) UnitResult else this
}
The terminating result makes it possible to to short-circuit calls to functions that require a value [A] when we've already met our terminating condition.
The non-terminating result is defined as:
trait SuccessfulResult[+A] extends Result[A] {
def apply[B](v: B): Result[B] = Successful(v)
def flatMap[A2 >: A, B](f: A2 => Future[Result[B]]): Future[Result[B]] = f(value)
}
case class Successful[+A](value: A) extends SuccessfulResult[A]
case object UnitResult extends SuccessfulResult[Unit] {
val value = {}
}
The non-teminating result makes it possible to provide the contained value [A] to functions. For good measure, I've also predefined a UnitResult for functions that are purely side-effecting, like goodDao.removeGood.
Now let's define your good, but terminating conditions:
case object UserNotFound extends Terminator
case object NotAuthenticated extends Terminator
case object GoodNotFound extends Terminator
case object NoOwnership extends Terminator
Now we have the tools to create the the workflow you were looking for. Each for comprehention wants a function that returns a Future[Result[A]] on the right-hand side, producing a Result[A] on the left-hand side. The flatMap on Result[A] makes it possible to call (or short-circuit) a function that requires an [A] as input and we can then map its result to a new Result:
def renderJson(data: Map[Any, Any]): JsResult = ???
def renderError(message: String): JsResult = ???
val resultFuture = for {
// apply UserNotFound to the Option to conver it into Result[User] or UserNotFound
userResult <- userDao.findUser(userId).map(UserNotFound(_))
// apply NotAuthenticated to AuthResult.ok to create a UnitResult or NotAuthenticated
authResult <- userResult.flatMap(user => userDao.authenticate(user).map(x => NotAuthenticated(x.ok)))
goodResult <- authResult.flatMap(_ => goodDao.findGood(goodId).map(GoodNotFound(_)))
// combine user and good, so we can feed it into checkOwnership
comboResult = userResult.combine(goodResult)
ownershipResult <- goodResult.flatMap { case (user, good) => goodDao.checkOwnership(user, good).map(NoOwnership(_))}
// in order to call removeGood with a good value, we take the original
// good result and potentially convert it to a Terminator based on
// ownershipResult via .given
_ <- goodResult.given(ownershipResult).flatMap(good => goodDao.removeGood(good).map(x => UnitResult))
} yield {
// ownership was the last result we cared about, so we apply the output
// to it to create a Future[Result[JsResult]] or some Terminator
ownershipResult(renderJson(Map(
"success" -> true
)))
}
// now we can map Result into its value or some other value based on the Terminator
val jsFuture = resultFuture.map {
case UserNotFound => renderError("User not found")
case NotAuthenticated => renderError("User not authenticated")
case GoodNotFound => renderError("Good not found")
case NoOwnership => renderError("No ownership")
case x => x.value
}
I know that's a whole lot of setup, but at least the Result type can be used for any Future for-comprehension that has terminating conditions.