Reputation: 1536
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?
Upvotes: 6
Views: 868
Reputation: 27356
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.
Upvotes: 2
Reputation: 14803
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.
Upvotes: 0
Reputation: 10882
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.
Upvotes: 0