Scala list validSelectValues not updated - scala

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}/>))
}

Related

combine two lists of different case class types

Combine two case class lists in to merged case class list
case class emp(emp_id:Integer,emp_name:String)
case class manager(manger_id:Integer,manager_name :String,emp_id:Integer)
case class combined(emp_id:Integer,emp_name :String,
manager_id:Integer,manager_name :String)
val list1:List[emp]= List((1,"emp1"),(2,"emp2")
val list2:List[manager]= List((101,"mgr1",1)(103,"mgr3",1))
expected output
val list3 = List(
(1,"emp1",101,"mgr1"),
(1,"emp1",103,"mgr3"),
(2,"emp2",null,null))
Depends on. If your data is already sorted by `emp_id and you have the same amount of managers as employees you can go with:
list1.zip(list2).map { case (e, m) =>
combined(e.emp_id, e.emp_name, m.manager_id, m.manager_name)
}
However, I suppose is not the case in a real-life scenario, where you need to match. Since the managers have an emp_id you can first run a groupBy on managers and then iterate over the employees to enrich them with manager input.
val grouped: Map[Int, manager] = list2.groupBy(_.emp_id)
list1.map { e =>
val manager_id = grouped.get(e.emp_id).flatMap (l =>
Try{l(0)}.toOption.map(_.manager_id)).getOrElse("null")
val manager_name = grouped.get(e.emp_id).flatMap (l =>
Try{l(0)}.toOption.map(_.manager_name)).getOrElse("null")
combined(e.emp_id, e.emp_name, manager_id, manager_name)
}
Did not checked the syntax, but you should get the point here.
P.S
Please use CamelCase and capital letters for classes in Scala.
Here's how I'd be tempted to tackle it.
// types
case class Emp(emp_id:Int, emp_name:String)
case class Manager(manager_id:Int, manager_name:String, emp_id:Int)
case class Combined(emp_id :Int
,emp_name :String
,manager_id :Option[Int]
,manager_name :String)
// input data
val emps :List[Emp] = List(Emp(1,"emp1"),Emp(2,"emp2"))
val mgrs :List[Manager] = List(Manager(101,"mgr1",1),Manager(103,"mgr3",1))
// lookup Emp name by ID
val empName = emps.groupMapReduce(_.emp_id)(_.emp_name)(_+_)
mgrs.map(mgr => Combined(mgr.emp_id
,empName(mgr.emp_id)
,Some(mgr.manager_id)
,mgr.manager_name)
) ++ empName.keySet
.diff(mgrs.map(_.emp_id).toSet)
.map(id => Combined(id, empName(id), None, ""))
//res0: List[Combined] = List(Combined(1, "emp1", Some(101), "mgr1")
// ,Combined(1, "emp1", Some(103), "mgr3")
// ,Combined(2, "emp2", None, ""))
I used Option[Int] and empty string "" to replace null, which Scala style tries to avoid.

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
}
}

Create map based on condition from Future of List in Scala

I have method with param type Future[List[MyRes]]. MyRes has two option fields id and name. Now I want to create map of id and name if both present. I am able to create map with default value as follow but I don't want to have default value just skip the entry with null value on either.
def myMethod(myRes: Future[List[MyRes]]): Future[Map[Long, String]] = {
myRes.map (
_.map(
o =>
(o.id match {
case Some(id) => id.toLong
case _ => 0L
}) ->
(o.name match {
case Some(name) => name
case _ => ""
})
).toMap)
Any suggestion?
You are looking for collect :)
myRes.map {
_.iterator
.map { r => r.id -> r.name }
.collect { case(Some(id), Some(name) => id -> name }
.toMap
}
If your MyRes thingy is a case class, then you don't need the first .map:
myRes.map {
_.collect { case MyRes(Some(id), Some(name)) => id -> name }
.toMap
}
collect is like .map, but it takes a PartialFunction, and skips over elements on which it is not defined. It is kinda like your match statement but without the defaults.
Update:
If I am reading your comment correctly, and you want to log a message when either field is a None, collect won't help with that, but you can do flatMap:
myRes.map {
_.flatMap {
case MyRes(Some(id), Some(name)) => Some(id -> name)
case x => loger.warn(s"Missing fields in $x."); None
}
.toMap
}
Try this:
def myMethod(myRes: Future[List[MyRes]]): Future[Map[Long, String]] = {
myRes.map (
_.flatMap(o =>
(for (id <- o.id; name <- o.name) yield (id.toLong -> name)).toList
).toMap
)
}
The trick is flattening List[Option[(Long,String)]] by using flatMap and converting the Option to a List.

Counting pattern in scala list

My list looks like the following: List(Person,Invite,Invite,Person,Invite,Person...). I am trying to match based on a inviteCountRequired, meaning that the Invite objects following the Person object in the list is variable. What is the best way of doing this? My match code so far looks like this:
aList match {
case List(Person(_,_,_),Invitee(_,_,_),_*) => ...
case _ => ...
}
First stack question, please go easy on me.
Let
val aList = List(Person(1), Invite(2), Invite(3), Person(2), Invite(4), Person(3), Invite(6), Invite(7))
Then index each location in the list and select Person instances,
val persons = (aList zip Stream.from(0)).filter {_._1.isInstanceOf[Person]}
namely, List((Person(1),0), (Person(2),3), (Person(3),5)) . Define then sublists where the lower bound corresponds to a Person instance,
val intervals = persons.map{_._2}.sliding(2,1).toArray
res31: Array[List[Int]] = Array(List(0, 3), List(3, 5))
Construct sublists,
val latest = aList.drop(intervals.last.last) // last Person and Invitees not paired
val associations = intervals.map { case List(pa,pb,_*) => b.slice(pa,pb) } ++ latest
Hence the result looks like
Array(List(Person(1), Invite(2), Invite(3)), List(Person(2), Invite(4)), List(Person(3), Invite(6), Invite(7)))
Now,
associations.map { a =>
val person = a.take(1)
val invitees = a.drop(1)
// ...
}
This approach may be seen as a variable size sliding.
Thanks for your tips. I ended up creating another case class:
case class BallotInvites(person:Person,invites:List[Any])
Then, I populated it from the original list:
def constructBallotList(ballots:List[Any]):List[BallotInvites] ={
ballots.zipWithIndex.collect {
case (iv:Ballot,i) =>{
BallotInvites(iv,
ballots.distinct.takeRight(ballots.distinct.length-(i+1)).takeWhile({
case y:Invitee => true
case y:Person =>true
case y:Ballot => false})
)}
}}
val l = Ballot.constructBallotList(ballots)
Then to count based on inviteCountRequired, I did the following:
val count = l.count(b=>if ((b.invites.count(x => x.isInstanceOf[Person]) / contest.inviteCountRequired)>0) true else false )
I am not sure I understand the domain but you should only need to iterate once to construct a list of person + invites tuple.
sealed trait PorI
case class P(i: Int) extends PorI
case class I(i: Int) extends PorI
val l: List[PorI] = List(P(1), I(1), I(1), P(2), I(2), P(3), P(4), I(4))
val res = l.foldLeft(List.empty[(P, List[I])])({ case (res, t) =>
t match {
case p # P(_) => (p, List.empty[I]) :: res
case i # I(_) => {
val head :: tail = res
(head._1, i :: head._2) :: tail
}
}
})
res // List((P(4),List(I(4))), (P(3),List()), (P(2),List(I(2))), (P(1),List(I(1), I(1))))

playframework 2 - empty string for optional form field

I have a form
case class UserUpdateForm(
id:Long, name: String,
remark: Option[String], location: Option[String])
I define the fields as
"id" -> of[Long],
"remarks" -> optional(text)
the remark field is None, Not Some("") I am excepting,
So, how can I bind an empty string to optional text field
case class OptionalText(wrapped: Mapping[String], val constraints: Seq[Constraint[Option[String]]] = Nil) extends Mapping[Option[String]] {
override val format: Option[(String, Seq[Any])] = wrapped.format
val key = wrapped.key
def verifying(addConstraints: Constraint[Option[String]]*): Mapping[Option[String]] = {
this.copy(constraints = constraints ++ addConstraints.toSeq)
}
def bind(data: Map[String, String]): Either[Seq[FormError], Option[String]] = {
data.keys.filter(p => p == key || p.startsWith(key + ".") || p.startsWith(key + "[")).map(k => data.get(k)).collect { case Some(v) => v }.headOption.map { _ =>
wrapped.bind(data).right.map(Some(_))
}.getOrElse {
Right(None)
}.right.flatMap(applyConstraints)
}
def unbind(value: Option[String]): (Map[String, String], Seq[FormError]) = {
val errors = collectErrors(value)
value.map(wrapped.unbind(_)).map(r => r._1 -> (r._2 ++ errors)).getOrElse(Map.empty -> errors)
}
def withPrefix(prefix: String): Mapping[Option[String]] = {
copy(wrapped = wrapped.withPrefix(prefix))
}
val mappings: Seq[Mapping[_]] = wrapped.mappings
}
val textOpt = new OptionalText(text)
Finally I copied the OptionalMapping class and exclude the empty filter part
I stumbled upon the same thing some months ago. I didn't find any easy way around it, so I decided to play along.
Basically, "remarks" -> optional(text)
will always return None when text is an empty string. So instead of treating an empty string as a sign of no updates, fill the remarks field in the form with the original value and then, after you get it back:
remarks match {
case None => // set remarks to ""
case originalRemark => // do nothing
case _ => // set remarks to the new value
}
Hope it helps. It's my first entry here, on Stack Overflow :)
Use
"remarks" -> default(text, "")