Running more than one Future in Action.async - scala

I tried in this way but it always go to case Nil.
def findByLastName(lastName:String)=Action.async {
val cursor = Json.obj("lastName" -> lastName)
StudentDaoAndEntity.findAllStudent(cursor) flatMap { lastName =>
ExaminationDao.findStudent(cursor) flatMap { lastName =>
LibraryDao.findStudent(cursor) map {
{
case Nil => Ok("Student Not Found")
case l: Seq[JsObject] => Ok(Json.toJson(l))
}
}
}
}
}
And my defined functions in Database are:
In StudentDaoAndEntity:
def findAllStudent(allStd: JsObject): Future[Seq[JsObject]] = {
// gather all the JsObjects in a list
collection.find(allStd).cursor[JsObject].collect[List]()
}
In LibraryDao:
def findStudent(allStd: JsObject): Future[Seq[JsObject]] = {
// gather all the JsObjects in a list
collection.find(allStd).cursor[JsObject].collect[List]()
}
In ExaminationDao:
def findStudent(allStd: JsObject): Future[Seq[JsObject]] = {
// gather all the JsObjects in a list
collection.find(allStd).cursor[JsObject].collect[List]()
}

The issue is with shadowing the lastName argument inside each lambda (as comments already pointed out). That being said, you can use a for-comprehension to make your code more readable
def findByLastName(lastName:String) = Action.async {
val cursor = Json.obj("lastName" -> lastName)
for {
_ <- StudentDaoAndEntity.findAllStudent(cursor)
_ <- ExaminationDao.findStudent(cursor)
students <- LibraryDao.findStudent(cursor)
} yield students match {
case Nil => Ok("Student Not Found")
case l: Seq[JsObject] => Ok(Json.toJson(l))
}
}

This is what I want:
def findByLastName(lastName:String)=Action.async {
request =>
val cursor = Json.obj("lastName" -> lastName)
StudentDaoAndEntity.findAllStudent(cursor) flatMap {
student =>
ExaminationDao.findStudent(cursor) flatMap {
examination =>
LibraryDao.findStudent(cursor) map {
{
case Nil => Ok("Student Not Found")
case library: Seq[JsObject] =>
val finalResult = student ++ examination ++ library
Ok(JsArray(finalResult))
}
}
}
}
}

Related

How to convert List[zio.Task[List[A]]] to zio.Task[[List[A]]

I need to process a set of Ids and return the result as zio.Task[List[RelevantReadingRow]]
def getBaselinesForRequestIds(baseLineReqIds: Set[String]): Task[List[RelevantReadingRow]] =
dynamoConnection
.run(
table.getAll("baseline_req_id" in baseLineReqIds)
)
.flatMap(_.toList.separate match {
case (err :: _, _) => ZIO.fail(new Throwable(describe(err)))
case (Nil, relevantReadings) => ZIO.succeed(relevantReadings)
})
The code above works but I need to process them in batches of 25 element as mutch.
I have try this but then I get a List of zio.Task
def getBaselinesForRequestIds(baseLineReqIds: Set[String]): Task[List[RelevantReadingRow]] = {
val subSet = baseLineReqIds.grouped(25).toList
val res = for {
rows <- subSet.map(reqIds => dynamoConnection
.run(
table.getAll("baseline_req_id" in reqIds)
).flatMap(e => e.toList.separate match {
case (err :: _, _) => ZIO.fail(new Throwable(describe(err)))
case (Nil, relevantReadings) => ZIO.succeed(relevantReadings)
}))
} yield rows
res // this is List[zio.Task[List[RelevantReadingRow]]]
}
I dont know how to convert back to a zio.Task[List[RelevantReadingRow]]
any sugestion?
You can use ZIO.collectAll to convert List[Task] to Task[List], I thought it was ZIO.sequence..., maybe I'm getting confused with cats...
Following example works with Zio2
package sample
import zio._
object App extends ZIOAppDefault {
case class Result(value: Int) extends AnyVal
val data: List[Task[List[Result]]] = List(
Task { List(Result(1), Result(2)) },
Task { List(Result(3)) }
)
val flattenValues: Task[List[Result]] = for {
values <- ZIO.collectAll { data }
} yield values.flatten
val app = for {
values <- flattenValues
_ <- Console.putStrLn { values }
} yield()
def run = app
}
In particular for your sample ...
and assuming that 'separate' it's just an extension method to collect some errors (returns a tuple of error list and result list), and ignoring the method 'describe' to turn an err into a throwable
https://scastie.scala-lang.org/jgoday/XKxVP2ECSFOv4chgSFCckg/7
package sample
import zio._
object App extends ZIOAppDefault {
class DynamoMock {
def run: Task[List[RelevantReadingRow]] = Task {
List(
RelevantReadingRow(1),
RelevantReadingRow(2),
)
}
}
case class RelevantReadingRow(value: Int) extends AnyVal
implicit class ListSeparate(list: List[RelevantReadingRow]) {
def separate: (List[String], List[RelevantReadingRow]) =
(Nil, list)
}
def getBaselinesForRequestIds(baseLineReqIds: Set[String]): Task[List[RelevantReadingRow]] = {
val dynamoConnection = new DynamoMock()
val subSet = baseLineReqIds.grouped(25).toList
val res: List[Task[List[RelevantReadingRow]]] = for {
rows <- subSet.map(reqIds => dynamoConnection
.run.flatMap(e => e.toList.separate match {
case (err :: _, _) => ZIO.fail(new Throwable(err))
case (Nil, relevantReadings) => ZIO.succeed(relevantReadings)
}))
} yield rows
for {
rows <- ZIO.collectAll(res)
} yield rows.flatten
}
val app = for {
values <- getBaselinesForRequestIds(Set("id1", "id2"))
_ <- Console.putStrLn { values }
} yield()
def run = app
}
So, here is an alternative solution based on #jgoday answer
def getBaselinesForRequestIds(baseLineReqIds: Set[String]): Task[List[RelevantReadingRow]] =
for {
values <- ZIO.foreachPar(baseLineReqIds.grouped(25).toList) {
reqIds =>
dynamoConnection
.run(
table.getAll("baseline_req_id" in reqIds)
).flatMap(e => e.toList.separate match {
case (err :: _, _) => ZIO.fail(new Throwable(describe(err)))
case (Nil, relevantReadings) => ZIO.succeed(relevantReadings)
})
}
} yield values.flatten

Handling a case of when my future returns a None in my play controller action method,

I want to refactor by update action below to look a little more readable and also handle the failure case better
The userService has the following functions:
class UserService {
def getUserByUsername: Future[Option[Int]] // which is the UserId
def getUserById: Future[User]
}
My action looks like:
def update(userId: Int) = Action.async { implicit request =>
request.body.validate[User] match {
case JsSuccess(user, _) => {
userService.getUserByUsername(user.username).map { userId =>
userService.getUserById(userId.get).map { existingUser =>
userService.update(user.username)
Ok
}
}
}
case JsError(err) => Future.sucessful(BadRequest(err))
}
}
How do I handle the situation where getUserByUsername returns a None?
Would this look cleaner if it was in a for comprehension, is it better style?
You have some missing data in your questions such as case classes for the User model, userService class.
also better to attach the original function.
Anyways, I will do something as follows:
def update(userId: Int) = Action { implicit request =>
request.body.validate[User] match {
case JsSuccess(user: User, _) => {
val userId = getUserByUsername(user.username)
userId match {
case Some(userId) => {
for {
_ <- userService.getUserById(userId)
_ <- userService.update(user.username)
} yield Ok
}.recover {
case t: Throwable =>
Metrics.errOnUpdate.increment() // Some metric to monitor
logger.error(s"update userId: $userId failed with ex: ${t.getMessage}") // log the error
InternalServerError(Json.toJson(Json.obj("error" -> "Failure occured on update"))) // return custom made exception to the client
}
case None => Future.successful(NotFound(s"No such user with ${user.username}"))
}
}
case JsError(err) => Future.sucessful(BadRequest(err))
}
}
Note: If .update returns Future, you actually not waiting to update before returning Ok to the user, thus, if its fails, its still returns Ok.
To fix that, use flatMap and then map the value of update response.
You can also separate the recovering for the getUserById and update if you prefer.
Edit:
def update(userId: Int) = Action { implicit request =>
request.body.validate[User] match {
case JsSuccess(user: User, _) => {
getUserByUsername(user.username).flatMap {
case Some(userId) => for {
_ <- userService.getUserById(userId)
_ <- userService.update(user.username)
} yield Ok
case None => Future.successful(NotFound(s"No such user with ${user.username}"))
}
}.recover {
case t: Throwable =>
Metrics.errOnUpdate.increment() // Some metric to monitor
logger.error(s"update userId: $userId failed with ex: ${t.getMessage}") // log the error
InternalServerError(Json.toJson(Json.obj("error" -> "Failure occured on update"))) // return custom made exception to the client
}
}
case JsError(err) => Future.sucessful(BadRequest(err))
}
}
First, you probably need to use Option.fold:
#inline final def fold[B](ifEmpty: => B)(f: A => B)
Then you can do something like this:
def update(userId: Int) = Action.async { implicit request =>
def handleJsonErrors(errors: Seq[(JsPath, collection.Seq[JsonValidationError])]): Future[Result] = ???
def updateUser(userWithoutId: User): Future[Result] = {
for {
userId <- userService.getUserByUsername(userWithoutId.username)
_ <- userService.getUserById(userId.get)
_ <- userService.update(userWithoutId.username)
} yield {
Ok
}
}
request.body.asJson.fold {
Future.successful(BadRequest("Bad json"))
} {
_.validate[User].fold(handleJsonErrors, updateUser).recover {
case NonFatal(ex) =>
InternalServerError
}
}
}

Possible ways to write for comprehension to handle multiple options

I have a function defined which return Option .
The below code works fine
def func :Option[Result]= {
(for {
src <- ele2
id <- ele
} yield {
src match {
case "test2" => Option(id.value).map(_.take(3))
.filterNot(_ == "de").map(id => Result(id))
case _ => None
}
}).getOrElse(None)
}
If I remove getOrElse(None) I get compile error that Iterable[Option[Result]] does not match expected type Option[Result]
I want to get rid of getOrElse(None).Is there a possible way to write the code
How about:
val res: Option[Result] = for {
src <- ele2
if src == "test2"
id <- ele
} yield Result(id.value)
For comprehensions in Scala is just syntactic sugar for flatMap and map. In some situations it is better to rewrite your for comprehensions using flatMap and map.
def func: Option[Result] = {
ele2.flatMap { src =>
ele.flatMap { id =>
src match {
case "test2" => Option(id.value).map(_.take(3))
.filterNot(_ == "de").map(id => Result(id))
case _ => None
}
}
}
}
The other option is just extract logic from yield to function
def func: Option[Result] = {
def innerLogic(src: String, id: Demo): Option[Result] = {
src match {
case "test2" => Option(id.value).map(_.take(3))
.filterNot(_ == "de").map(id => Result(id))
case _ => None
}
}
for {
src <- ele2
id <- ele
res <- innerLogic(src, id)
} yield {
res
}
}

scala Returns Future[Unit] instead of Future[ContentComponentModel]

I have the following code:
def getContentComponents: Action[AnyContent] = Action.async {
val test = contentComponentDTO.list().map { contentComponentsFuture =>
contentComponentsFuture.foreach(contentComponentFuture =>
contentComponentFuture.typeOf match {
case 1 =>
println("blubb")
case 5 =>
contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(
text => {
contentComponentFuture.text = text.text
println(text.text)
println(contentComponentFuture.text)
}
)
}
)
}
Future.successful(Ok(Json.obj("contentComponents" -> test)))
}
and I got this error message:
The .list() method should return a Future[ContentComponentModel]
def list(): Future[Seq[ContentComponentModel]] = db.run {
whats my mistake in this case?
thanks
Your contentComponentsFuture should be of type Seq[ContentComponentModel]. In this case You should move
Future.successful(Ok(Json.obj("contentComponents" -> test)))
just into the map expression (which is async) after loop.
It should looks something like:
def getContentComponents: Action[AnyContent] = Action.async {
val test = contentComponentDTO.list().map { contentComponents =>
contentComponents.foreach(contentComponentFuture =>
contentComponentFuture.typeOf match {
case 1 =>
println("blubb")
case 5 =>
contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(
text => {
contentComponentFuture.text = text.text
println(text.text)
println(contentComponentFuture.text)
}
)
}
)
Future.successful(Ok(Json.obj("contentComponents" -> contentComponents)))
}
}

Scala: Convert Future[Option[List[User]]] to Future[Map[Long, User]]

User is a case class:
case class User(id: Long, firstName: String, lastName: String)
A search function returns Future[Option[List[User]]]. From this future I want to extract a Map[Long, User] with the id as key. Right now I am using for comprehension the following way to extract the Map but there should be a better way to do it.
def users: Future[Map[Long, User]] = {
for {
future <- search(...)
} yield for {
users <- future
} yield for {
user <- users
} yield user.id -> user
}.map(_.map(_.toMap))
What about this?
def users: Future[Map[Long, User]] = search().map { opt =>
val list = for {
users <- opt.toList
user <- users
} yield (user.id, user)
list.toMap
}
I think this should do what you need:
def users: Future[Map[Long, User]] = {
search().map { searchResult =>
searchResult match {
case Some(list) => list.map { u => (u.id, u) }.toMap
case _ => Map.empty
}
}
}