Handle Future[Option[T]] when traversing a List - scala

def getCommentIds(
commentTargetId: Long,
sortOrder: CommentOrderEnum): Future[Seq[CommentStatsBO]]
def getCommentDetail(commentId: Long): Future[Option[CommentDetailDTO]]
def getCommentListWithDetail(
targetId: Long,
sortOrder: CommentOrderEnum,
page: Int): Future[Seq[CommentDetailDTO]] = {
commentModel.getCommentIds(targetId, sortOrder).flatMap {
commentStatsBOSeq =>
Future.traverse(commentStatsBOSeq) { commentStatsBO =>
// commentDetail is a Future[Option[T]]
val commentDetail = getCommentDetail(commentStatsBO.iId)
commentDetail.map(commentOpt =>
commentOpt
// merge the stat info into the comment detail
.map(_.copy(replyCount = Some(commentStatsBO.replyCount)))
.getOrElse(CommentDetailDTO))
}
}
}
case class CommentDetailDTO(
id: Long,
author: JsObject,
detail: CommentDetail,
replyCount: Option[Int] = None
)
Firstly, the function getCommentIds returns a sequence of CommentStatsBO, then traversing it and try to get detail for every comment. Here comes the question, getCommentDetail returns a Future which contains an option since the comment maybe not found, in this case, how to filter those ones whose option is None? I have tried getOrElse , but don't know how to define an empty object just like Json.obj() since case class doesn't support.
Thanks!

Don't try to do too many things at the same time, rather build the solution you need step by step.
If you do a simple Future.traverse using just getCommentDetail you will get a Future[Seq[Option[CommentDetailDTO]]] which then you can map and use collect with the Seq to remove the Option
def getCommentListWithDetail(
targetId: Long,
sortOrder: CommentOrderEnum,
page: Int
): Future[Seq[CommentDetailDTO]] =
commentModel.getCommentIds(targetId, sortOrder).flatMap { commentStatsBOSeq =>
Future.traverse(commentStatsBOSeq) { commentStatsBO =>
getCommentDetail(commentStatsBO.iId)
} map { commentOptionalDetails =>
commentOptionalDetails.collect {
case Some(commentDetail) => commentDetail
}
}
}
Or if you use cats, you can use traverseFilter
import cats.syntax.all._
def getCommentListWithDetail(
targetId: Long,
sortOrder: CommentOrderEnum,
page: Int
): Future[Seq[CommentDetailDTO]] =
commentModel.getCommentIds(targetId, sortOrder).flatMap { commentStatsBOSeq =>
commentStatsBOSeq.traverseFilter(getCommentDetail)
}

Related

Using Scala groupBy(), from method fetchUniqueCodesForARootCode(). I want to get a map from rootCodes to lists of uniqueCodes

I want to to return Future[Map[String, List[String]]] from fetchUniqueCodesForARootCode method
import scala.concurrent._
import ExecutionContext.Implicits.global
case class DiagnosisCode(rootCode: String, uniqueCode: String, description: Option[String] = None)
object Database {
private val data: List[DiagnosisCode] = List(
DiagnosisCode("A00", "A001", Some("Cholera due to Vibrio cholerae")),
DiagnosisCode("A00", "A009", Some("Cholera, unspecified")),
DiagnosisCode("A08", "A080", Some("Rotaviral enteritis")),
DiagnosisCode("A08", "A083", Some("Other viral enteritis"))
)
def getAllUniqueCodes: Future[List[String]] = Future {
Database.data.map(_.uniqueCode)
}
def fetchDiagnosisForUniqueCode(uniqueCode: String): Future[Option[DiagnosisCode]] = Future {
Database.data.find(_.uniqueCode.equalsIgnoreCase(uniqueCode))
}
}
getAllUniqueCodes returns all unique codes from data List.
fetchDiagnosisForUniqueCode returns DiagnosisCode when uniqueCode matches.
From fetchDiagnosisForUniqueCodes, I am returningFuture[List[DiagnosisCode]] using getAllUniqueCodes() and fetchDiagnosisForUniqueCode(uniqueCode).*
def fetchDiagnosisForUniqueCodes: Future[List[DiagnosisCode]] = {
val xa: Future[List[Future[DiagnosisCode]]] = Database.getAllUniqueCodes.map { (xs:
List[String]) =>
xs.map { (uq: String) =>
Database.fetchDiagnosisForUniqueCode(uq)
}
}.map(n =>
n.map(y=>
y.map(_.get)))
}
xa.flatMap {
listOfFuture =>
Future.sequence(listOfFuture)
}}
Now, def fetchUniqueCodesForARootCode should return Future[Map[String, List[DiagnosisCode]]] using fetchDiagnosisForUniqueCodes and groupBy
Here is the method
def fetchUniqueCodesForARootCode: Future[Map[String, List[String]]] = {
fetchDiagnosisForUniqueCodes.map { x =>
x.groupBy(x => (x.rootCode, x.uniqueCode))
}
}
Need to get the below result from fetchUniqueCodesForARootCode:-
A00 -> List(A001, A009), H26 -> List(H26001, H26002), B15 -> List(B150, B159), H26 -> List(H26001, H26002)
It's hard to decode from the question description, what the problem is. But if I understood correctly, you want to get a map from rootCodes to lists of uniqueCodes.
The groupBy method takes a function that for every element returns its key. So first you have to group by the rootCodes and then you have to use map to get the correct values.
groupBy definition: https://dotty.epfl.ch/api/scala/collection/IterableOps.html#groupBy-f68
scastie: https://scastie.scala-lang.org/KacperFKorban/PL1X3joNT3qNOTm6OQ3VUQ

Play Slick: How to fetch selected fields from a DB table in play-slick 3.0.0?

I am new to play framework. I am working on a play slick based application where I want to fetch a list of objects from DB which will contains some selected fields. For fetching all the fields I am using following code:
case class Mail(txID: String,
timeStamp: Long,
toUserID: String,
mailContent: String,
mailTemplateFileName: String,
fromID: String,
toID: String
)
def getLogFromIDFuture(userID: String): Future[Option[List[Mail]]] = cache.getOrElseUpdate[Option[List[Mail]]](userID) {
val resultingUsers = db.run(mailsData.filter(x => x.toUserID === userID).result)
val res = Await.result(resultingUsers, Duration.Inf)
res.map(t => t) match {
case t if t.nonEmpty =>
Future(Some(t.toList))
case _ => Future(None)
}
}
So my question is how to fetch only timeStamp, toUserID, mailContent, fromID, toID fields as the list of objects like UserMessage(timeStamp: Long, toUserID: String, mailContent: String, fromID: String, toID: String). I tried searching about this but didn't get any convincing answers.
Like I said in the comment you can do this:
def getLogFromIDFuture(userID: String): Future[Option[List[UserMessage]]] = cache.getOrElseUpdate[Option[List[Mail]]](userID) {
val resultingUsers = db.run(mailsData.filter(x => x.toUserID === userID).map(entry =>(entry.timeStamp, entry.toUserID, entry.mailContent, entry.fromID, entry.toID))
.result)// here you have the tuple of things
// add the mapping of the tuple to the UserMessage
val res = Await.result(resultingUsers, Duration.Inf)
res.map(t => t) match {
case t if t.nonEmpty =>
Future(Some(t.toList))
case _ => Future(None)
}
}
You can get rid of that Await.result
resultingUsers.map( match {
case t if t.nonEmpty =>
Some(t.toList)
case _ => None
}
)
Hope it helps.

Working with options in Scala (best practices)

I have a method that I wrote to enrich person data by performing an API call and adding the enriched data.
I have this case class:
case class Person(personData: PersonData, dataEnrichment: Option[DataEnrichment])
My method is supposed to return this case class, but I have few filters before, in case person height is not "1.8 m" OR if personId was not found in the bio using the regex, I want to return Person with dataEnrichment = None . My issue is that person height and personId are Options themselves, so it looks like this:
def enrichPersonObjWithApiCall(person: Person) = {
person.personData.height.map(_.equals("1.8 m")) match {
case Some(true) =>
val personId = person.personData.bio flatMap { comment =>
extractPersonIdIfExists(comment)
}
personId match {
case Some(perId) =>
apiCall(perId) map { apiRes =>
Person(
person.personData,
dataEnrichment = apiRes)
}
case _ =>
Future successful Person(
person.personData,
dataEnrichment = None)
}
case _ =>
Future successful Person(
person.personData,
dataEnrichment = None)
}
}
def extractPersonIdIfExists(personBio: String): Option[String] = {
val personIdRegex: Regex = """(?<=PersonId:)[^;]+""".r
personIdRegex.findFirstIn(personBio)
}
def apiCall(personId: String): Future[Option[DataEnrichment]] = {
???
}
case class DataEnrichment(res: Option[String])
case class PersonData(name: String, height: Option[String], bio: Option[String])
It doesn't seem to be a Scala best practice to perform it like that. Do you have a more elegant way to get to the same result?
Using for is a good way to process a chain of Option values:
def enrichPersonObjWithApiCall(person: Person): Future[Person] =
(
for {
height <- person.personData.height if height == "1.8 m"
comment <- person.personData.bio
perId <- extractPersonIdIfExists(comment)
} yield {
apiCall(perId).map(Person(person.personData, _))
}
).getOrElse(Future.successful(Person(person.personData, None)))
This is equivalent to a chain of map, flatMap and filter calls, but much easier to read.
Here, I tried to make it more idiomatic and shorter:
def enrichPersonObjWithApiCall(person: Person) = {
person.personData.height.collect {
case h if h == "1.8 m" =>
val personId = person.personData.bio.flatMap(extractPersonIdIfExists)
personId.map(
apiCall(_)
.map(apiRes => person.copy(dataEnrichment = apiRes))
)
}.flatten.getOrElse(
Future.successful(person.copy(dataEnrichment = None))
)
}
Basically, the idea is to use appropriate monadic chains of map, flatMap, collect instead of pattern matching when appropriate.
Same idea as Aivean's answer. Just I would use map flatMap and filter.
def enrichPersonObjWithApiCall(person: Person) = {
person.personData.height
.filter(_ == "1.8 m")
.flatMap{_=>
val personId = person.personData.bio
.flatMap(extractPersonIdIfExists)
personId.map(
apiCall(_)
.map(apiRes => person.copy(dataEnrichment = apiRes))
)
}.getOrElse(Future.successful(person))
}
It's more readable for me.

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.

Dynamic orderBy with Squeryl

I can not figure out how to change orderBy dynamically in runtime. I need something like:
def samplesSorted(fields: List[String]) = {
from(Schema.samples)(s => select(s) orderBy(fields.map(getterByName))
}
Or something like
def samplesSorted(fields: List[String]) = {
val q = from(Schema.samples)(s => select(s))
fields.forEach(field => q.addOrderBy(getterByName(field)))
q
}
I am trying to write a help function to manipulate AST now. But that does not seem like the right solution.
Did not notice there is a version of orderBy that accepts a list of ExpressionNodes. Was able to solve it like this:
def samplesSorted(fields: List[String]) = {
from(Schema.samples)(s => select(s) orderBy(fields.map(buildOrderBy(s)))
}
def buildOrderBy(row: Row)(field: String): ExpressionNode = {
getterByName(row, field)
}
def getterByName(row: Row, field: String): String = field match {
case "Name" => row.name
case "Address" => row.address
}
Have not tried with fields of different types yet - implicits may not work in this case. But I could always call them explicitly.
Upd:
To do the same with descending order one could use a helper like this one:
def desc(node: ExpressionNode):ExpressionNode = new OrderByArg(node) {desc}
This works for me
def ord(dr: DataRow, name: String): ExpressionNode = if (orderAscending) {
dr.getterByName(name) asc
} else {
dr.getterByName(name) desc
}
case class DataRow(id: Long,
#Column("resource_id") resourceId: String,
def getterByName(name: String) = {
name match {
case "resource_id" => resourceId.~
case _ => id.~
}
}
}
from(DataSchema.dataRows) { dr =>
where(dr.id === id).select(dr).orderBy(ord(dr, filedName))
}.page(offset, limit)