better way to dissolve multiple Either arguments - scala

I have a model, that looks like this:
case class ItemEntity private(
userId: IdValue,
title: TitleValue,
description: DescriptionValue,
media: MediaValue,
price: MoneyValue,
stock: StockValue
)
And in the companion object of this model, is a constructor, that currently looks like this:
object ItemEntity
{
def fromUnsafe(
mayBeUserId: Either[IdError, IdValue],
mayBeTitle: Either[TitleError, TitleValue],
mayBeDescription: Either[DescriptionError, DescriptionValue],
mayBeMedia: Either[MediaError, MediaValue],
mayBePrice: Either[MoneyError, MoneyValue],
mayBeStock: Either[StockError, StockValue]
): Either[ItemError, ItemEntity] =
{
mayBeUserId match
case Right(id) => mayBeTitle match
case Right(title) => mayBeDescription match
case Right(description) => mayBeMedia match
case Right(media) => mayBePrice match
case Right(price) => mayBeStock match
case Right(stock) => Right(
ItemEntity(
userId = id,
title = title,
description = description,
media = media,
price = price,
stock = stock
)
)
case Left(stockError) => Left(ItemError.Error(stockError))
case Left(priceError) => Left(ItemError.Error(priceError))
case Left(mediaError) => Left(ItemError.Error(mediaError))
case Left(descriptionError) => Left(ItemError.Error(descriptionError))
case Left(titleError) => Left(ItemError.Error(titleError))
case Left(idError) => Left(ItemError.Error(idError))
}
}
As you see, the code has a lot of boilerplate.
But I cannot think of a better way to wirte this constructor...
So my question is:
Is there a better way, to write this?

You can use for-comprehension but first you will have to map or widen your left value to the same type:
def fromUnsafe(
mayBeUserId: Either[IdError, IdValue],
mayBeTitle: Either[TitleError, TitleValue],
mayBeDescription: Either[DescriptionError, DescriptionValue],
mayBeMedia: Either[MediaError, MediaValue],
mayBePrice: Either[MoneyError, MoneyValue],
mayBeStock: Either[StockError, StockValue]
): Either[ItemError, ItemEntity] =
for {
id <- mayBeUserId.left.map(ItemError.Error)
title <- mayBeTitle.left.map(ItemError.Error)
description <- mayBeDescription.left.map(ItemError.Error)
media <- mayBeMedia.left.map(ItemError.Error)
price <- mayBePrice.left.map(ItemError.Error)
stock <- mayBeStock.left.map(ItemError.Error)
} yield ItemEntity(id, title, description, media, price, stock)
If the error types can be widen to the same type then you can also move the left.map after the for-comprehension.
(for {
id <- mayBeUserId
title <- mayBeTitle
description <- mayBeDescription
media <- mayBeMedia
price <- mayBePrice
stock <- mayBeStock
} yield ItemEntity(id, title, description, media, price, stock)).left.map(ItemError.Error)
You can also replace left.map with leftMap and/or leftWiden with Cats.

Related

How to merge two case class sequences based on an id in Scala?

I have a case class which is list of restaurants:
case class RestaurantSearchResponse(
restaurant: Restaurant,
city: CityDetail
)
case class Restaurant(
id: Long,
name: String,
rating: BigDecimal
)
RestaurantSearchResponse is what we return at the moment from API. Now I have another case class that has list of restaurants with the sales_count like below:
case class HighSellerSort(
id: Long = 0L,
restaurantId: Long,
orders_count: Int
)
So now on API side I have sequence of restaurants and also a sequence of HighSellerSort. They can get merged based on restaurantId which is present on both case classes. How can I merge them and then sort based on orders_count.
NOTE: There might not be a record in HighSellerSort for a specific restaurant.
The important first step is to convert your sales sequence into a Map, so that your logic does not become quadratic, like it is in the accepted answer.
val lookup = sales
.map { s => s.restaurant_id -> s.orders_count }
.toMap
.withDefault(_ => 0)
Now, if you just need to sort:
restaurants.sortBy { r => lookup(r.id) }
Or if you want to tag each restaurant with a count:
restaurants.map { r => r -> r.lookup(r.id) }
Or if you want both
restaurants.map { r => r -> r.lookup(r.id) }.sortBy(_._2)
Basically you need to iterate over your restaurants, and look for a HighSellerSort that corresponds to the restaurant. So based on my assumptions about your questions, this can be the approach:
def merge(
restaurants: Seq[Restaurant],
sorts: Seq[HighSellerSort]
): Seq[(Restaurant, Option[HighSellerSort])] = {
restaurants.map { restaurant =>
restaurant -> sorts.collectFirst {
case sort if sort.restaurantId == restaurant.id => sort
}
}
}

How can I create multiple Datasets with different class types from one general Dataset?

I have a Dataset[General] which I would like to split into two Datasets. One Dataset[Mary] and one Dataset[John]. I would like to have as many rows based the amount of structures that exist in the add_marry or add_john arrays. I want to use Datasets and therefore not use the Dataframe API which would allow me to explode. Any advice on this would be greatly appreciated! The input is
List(General("1","Bob",[{"1","2"},{"1","3"}],[{"11","12"},{"21","23"}])).toDS
And output would be
Dataset[John] = (("1","Bob","11","12"),("1","Bob","21","12"))
Dataset[Mary] = (("1","Bob","1","2"),("1","Bob","1","3"))
Classes =
case class General (
id: String,
name: String,
add_marry: Array[AddressMary],
add_john: Array[AddressJohn]
)
case class AddressMary (
streetUK: String,
houseUK: String
)
case class AddressJohn (
streetUS: String,
houseUS: String
)
case class John (
id: String,
name: String,
street: String,
house: String)
case class Mary (
id: String,
name: String,
street: String,
house: String)
I have tried to do a for loop but this doesnt seem to work as I am not getting a row for each address that they have.
val dsHome = dfHome.as[General]
.flatMap(pf => {
if (pf.add_marry.size > 0) for (e <- pf) {
Mary(pf.id,pf.name,e.streetUK, e.houseUK)
}
I suppose this is what you were trying to do:
val dfHome: DataFrame = ???
val dsHome: Dataset[General] = dfHome.as[General]
val dsMary1: Dataset[Mary] = dsHome.flatMap { case General(id, name, addrs, _) =>
addrs.map { case AddressMary(street, house) => Mary(id, name, street, house) }
}
val dsJohn1: Dataset[John] = dsHome.flatMap { case General(id, name, _, addrs) =>
addrs.map { case AddressJohn(street, house) => John(id, name, street, house) }
}
You can also rewrite it with for-comprehension as:
val dsMary2: Dataset[Mary] =
for {
General(id, name, addrs, _) <- dsHome
AddressMary(street, house) <- addrs
} yield Mary(id, name, street, house)
val dsJohn2: Dataset[John] =
for {
General(id, name, _, addrs) <- dsHome
AddressJohn(street, house) <- addrs
} yield John(id, name, street, house)
but you will need the plugin better-monadic-for as withFilter isn't implemented for Dataset.
EDIT: Author asked for a way to get the Dataset of John and Mary at one go. We could zip the inner arrays but this would require every element one array to have a corresponding element in the other array in the same order. We could also nest the flatMaps together but that would be equivalent to a cartesian join.
val dsMaryJohnZip1: Dataset[(Mary, John)] = dsHome.flatMap { case General(id, name, addrMs, addrJs) =>
addrMs.zip(addrJs).map { case (AddressMary(sM, hM), AddressJohn(sJ, hJ)) => (Mary(id, name, sM, hM), John(id, name, sJ, hJ)) }
}
val dsMaryJohnZip2: Dataset[(Mary, John)] =
for {
General(id, name, addrMs, addrJs) <- dsHome
(AddressMary(sM, hM), AddressJohn(sJ, hJ)) <- addrMs.zip(addrJs)
} yield (Mary(id, name, sM, hM), John(id, name, sJ, hJ))
val dsMaryJohnCartesian1: Dataset[(Mary, John)] = dsHome.flatMap { case General(id, name, addrMs, addrJs) =>
addrMs.flatMap { case AddressMary(sM, hM) =>
addrJs.map { case AddressJohn(sJ, hJ) =>
(Mary(id, name, sM, hM), John(id, name, sJ, hJ))
}
}
}
val dsMaryJohnCatesian2: Dataset[(Mary, John)] =
for {
General(id, name, addrMs, addrJs) <- dsHome
AddressMary(sM, hM) <- addrMs
AddressJohn(sJ, hJ) <- addrJs
} yield (Mary(id, name, sM, hM), John(id, name, sJ, hJ))

How to flatten a case class with a list value to another case class properly with scala

I have case classes of Contact and Person:
case class Contact(id: String, name: String)
case class Person(id: String, name: String, age: Int, contacts: List[Contact])
lets say I have list of Person:
val pesonList = List(
Person(1, "john", 30, List(Contact(5,"mark"),Contact(6,"tamy"),Contact(7,"mary"))),
Person(2, "jeff", 40, List(Contact(8,"lary"),Contact(9,"gary"),Contact(10,"sam")))
)
I need to flatten this pesonList and transform it to list of:
case class FlattenPerson(personId: String, contactId: Option[String], personName: String)
so the results would be:
val flattenPersonList = List(
FlattenPerson(1,"john"),
FlattenPerson(1,5,"mark"),
FlattenPerson(1,6,"tamy"),
FlattenPerson(1, 7"mary"),
FlattenPerson(2,"jeff"),
FlattenPerson(2,8,"lary"),
FlattenPerson(2,9,"gary"),
FlattenPerson(2,10,"sam")
)
I found one way that looks like its working but dosent seem like the right way...it might break and scala probably have a more efficient way.
this is what I could come up with:
val people = pesonList.map(person => {
FlattenPerson(person.id, None, person.name)
})
val contacts = pesonList.flatMap(person => {
person.contacts.map(contact => {
FlattenPerson(person.id, Some(contact.id), contact.name)
})
})
val res = people ++ contacts
this would also have bad performance, I need to do it for each api call my app gets and it can be allot of calls plus i need to filter res.
would love to get some help here
I think flatMap() can do what you're after.
personList.flatMap{pson =>
FlattenPerson(pson.id, None, pson.name) ::
pson.contacts.map(cntc => FlattenPerson(pson.id, Some(cntc.id), cntc.name))
}
//res0: List[FlattenPerson] = List(FlattenPerson(1,None,john)
// , FlattenPerson(1,Some(5),mark)
// , FlattenPerson(1,Some(6),tamy)
// , FlattenPerson(1,Some(7),mary)
// , FlattenPerson(2,None,jeff)
// , FlattenPerson(2,Some(8),lary)
// , FlattenPerson(2,Some(9),gary)
// , FlattenPerson(2,Some(10),sam))
For reference here is a recursive versions of this algorithm that includes filtering in a single pass. This appears to perform somewhat faster than calling .filter(f) on the result. The non-filtered recursive version has no real performance advantage.
def flattenPeople(people: List[Person], f: FlattenPerson => Boolean): List[FlattenPerson] = {
#annotation.tailrec
def loop(person: Person, contacts: List[Contact], people: List[Person], res: List[FlattenPerson]): List[FlattenPerson] =
contacts match {
case Contact(id, name) :: tail =>
val newPerson = FlattenPerson(person.id, Some(id), name)
if (f(newPerson)) {
loop(person, tail, people, newPerson +: res)
} else {
loop(person, tail, people, res)
}
case _ =>
val newPerson = FlattenPerson(person.id, None, person.name)
val newRes = if (f(newPerson)) newPerson +: res else res
people match {
case p :: tail =>
loop(p, p.contacts, tail, newRes)
case Nil =>
newRes.reverse
}
}
people match {
case p :: tail => loop(p, p.contacts, tail, Nil)
case _ => Nil
}
}

Scala Anorm zero to many the right way

I have a simple database consisting of 2 tables - movie and comment, where comments are related to movies, and then I have following piece of scala anorm code:
case class Comment(commentId: Long, comment: String)
case class Movie(movieId: Long, name: String, movieType: String)
object MovieDao {
val movieParser: RowParser[Movie] = {
long("movieId") ~
str("name") ~
str("movieType") map {
case movieId ~ name ~ movieType => Movie(movieId, name, movieType)
}
}
val commentParser: RowParser[Comment] = {
long("commentId") ~
str("comment") map {
case commentId ~ comment => Comment(commentId, comment)
}
}
def getAll(movieType: String) = DB.withConnection {
implicit connection =>
SQL(
"""
|SELECT
|movie.movieId,
|movie.name,
|movie.movieType,
|comment.commentId,
|comment.comment
|FROM movie
|LEFT JOIN comment USING(movieId)
|WHERE movieType = {movieType}
""".stripMargin)
.on("movieType" -> movieType)
.as(((movieParser ~ (commentParser ?)) map (flatten)) *)
.groupBy(_._1) map {(mc: (Movie, List[(Movie, Option[Comment])])) =>
mc match {
case (a, b) => (a, b filter { //filter rows with no comments
case (c, Some(d)) => true
case _ => false
} map(_._2))
}
} toList
}
}
My goal is to return List[(Movie, Option[List[Comment]])] from getAll method, so I can iterate over movies and check if there are any comments as simple as possible, e.i. match None or Some on comments List. I'm currently returning List[(Movie, Option[List[Option[Comment]])] and I'm only able to check size of comments List (thanks to using filter method), which I don't consider as the right way to do it in scala.
My second question is about parsing query itself, I think it's just to complicated the way I did it. Is there any simpler and nicer solution to parse 0..N relation using anorm?
Peter, it's possibly more style than anything dramatically different, but with a MovieComments case class, you could write something like:
case class MovieComments(movie: Movie, comments: List[Comment])
val movieCommentsP =
movieParser ~ (commentParser ?) map {
case movie ~ comment =>
MovieComments(movie,if (comment.isEmpty) List() else List(comment.get))
}
val movieSqlSelector = "m.movieId, m.name, m.movieType"
val commentSqlSelector = "c.commentId, c.comment"
def getAll(movieType: String) :List[MovieComments]= DB.withConnection {
implicit connection =>
(SQL(
s"""
|SELECT
|$movieSqlSelector,
|$commentSqlSelector
|FROM movie
|LEFT JOIN comment USING(movieId)
|WHERE movieType = {movieType}
""".stripMargin)
.on('movieType -> movieType)
.as(movieCommentsP *)
.groupBy(_.movie.movieId) map {
case (movieId,movieComments) =>
MovieComments(
movieComments.head.movie,
movieComments.flatMap(_.comments))
}
).toList
}
You may really need an Option[List[Comment]], but wouldn't a List[Comment] do? List() is the "no comment" case after all. (P.S. I find the use of sqlSelector variables helps with refactoring.)

Scala list validSelectValues not updated

This is my code in a class Building:
object province extends MappedLongForeignKey(this, Province) {
override def dbColumnName = "province_id"
def selectValues: Box[List[(String, String)]] =
Full((("0", "")) +: Province.findAll(OrderBy(Province.name, Ascending)).
map( p => (p.id.is.toString, p.name.is)))
override def _toForm: Box[Elem] = Full(selectValues.flatMap{
case Nil => Empty
case xs => Full(SHtml.ajaxSelect(xs, Full(this.is.toString), v => {
this.set(v.toLong)
JsCmds.ReplaceOptions("council_select",
councilsList(v).map(c => (c.id.is.toString, c.name.is)), None)
}))
}.openOr(<span>{"sin provincias"}</span>))
private def councilsList(p: String) = p match {
case id if id != "" =>
Council.findAll(By(Council.province, p.toLong),
OrderBy(Council.name, Ascending))
case _ => List()
}
}
// Council
object council extends MappedLongForeignKey(this, Council) {
override def dbColumnName = "council_id"
val id = "council_select"
override def validSelectValues: Box[List[(Long, String)]] =
Full(((0l, "")) +: Council.findAll(By(Council.province, province),
OrderBy(Council.name, Ascending)).
map( c => (c.id.is, c.name.is)))
override def _toForm: Box[Elem] = Full(validSelectValues.flatMap{
case Nil => Empty
case xs => Full(SHtml.selectObj(xs, Full(this.is), this.set, "id" -> id))
}.openOr(<select id={id}/>))
}
in the edit form, when I change on the list the province, council list is replaced by province filter perfectly, but when i save building, councill is not saved/updated the first time. the next times, it save perfect because the province is seted, until i change province other time... So, when I set a new province, the councill isnt saved, I need to save 2 times, 1 for province, and 1 for council.
I think that the problem is because validSelectValues of object council, is loaded the first time i load the page, and it will be filtering by the province seted, or null list when province is not set... how can I reload the council validSelectValues when i change the province list?
Thanks
Try replacing your call to ajaxSelect with ajaxUntrustedSelect. The normal select options in Lift check that the value being submitted is trusted, IE: that it was one of the options available when the page was generated. If you are using javascript or other means to mutate the list, you can avoid that check by using the Untrusted version of the component.
The solution that I have find...
object council extends MappedLongForeignKey(this, Council) {
override def dbColumnName = "council_id"
val id = "council_select"
def selectValues: Box[List[(String, String)]] = province.obj match {
case Full(o) => Full( Council.findAll(By(Council.province, o),
OrderBy(Council.name, Ascending)).
map( c => (c.id.is.toString, c.name.is)))
case _ => Full((("0","")) Council.findAll(OrderBy(Council.name, Ascending)).
map( c => (c.id.is.toString, c.name.is)))
}
override def _toForm: Box[Elem] = Full(selectValues.flatMap{
case Nil => Empty
case xs => Full(SHtml.untrustedSelect(xs, Full(this.is.toString), v => {this.set(v.toLong)}, "id" -> id))
}.openOr(<select id={id}/>))
}