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

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.

Related

Comparing the json data types at runtime using Jackson and Scala

I have an incoming JSON data that looks like below:
{"id":"1000","premium":29999,"eventTime":"2021-12-22 00:00:00"}
Now, I have created a class that will accept this record and will check whether the data type of the incoming record is according to the data types defined in the case class. However, when I am calling the method it is always calling the Failure part of the match case.
case class Premium(id: String, premium: Long, eventTime: String)
class Splitter extends ProcessFunction[String, Premium] {
val outputTag = new OutputTag[String]("failed")
def fromJson[T](json: String)(implicit m: Manifest[T]): Either[String, T] = {
Try {
println("inside")
lazy val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
mapper.readValue[T](json)
} match {
case Success(x) => {
Right(x)
}
case Failure(err) => {
Left(json)
}
}
}
override def processElement(i: String, context: ProcessFunction[String, Premium]#Context, collector: Collector[Premium]): Unit = {
fromJson(i) match {
case Right(data) => {
collector.collect(data)
println("Good Records: " + data)
}
case Left(json) => {
context.output(outputTag, json)
println("Bad Records: " + json)
}
}
}
}
Based on the sample record above, it should pass the Success value but no matter what I pass, it always enters the Failure part. What else is missing?
I am using Scala 2.11.12 and I tried examples from this link and this link but no luck.

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

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

return type for findAndModify with futures

Say I'm using an async library to read and write data. If a failure, I get an error message as a string, otherwise the data. Is there a better way to represent the return type than Future[Either[String, Future[Data]]]?
Possible implementation:
def createData(name: String): Future[Either[String, Future[Data]]] = {
dataDAO.findOneByName(name).flatMap {
case Some(data) =>
Future.successful(Left(s"Data with name already exists. $name"))
case None =>
val data = Data.createFromName(name)
dataDAO.save(data).map {
lastError =>
data
}.right.futureSuccessful
}
}
Your return type should be Future[Either[String, Data]]
To archive this you need to change your second case to:
case None =>
val data = Data.createFromName(name)
dataDAO.save(data).map {
_ => Right(data)
}
I even would improve your error type from String to e.g. CreateDataError, so your return type would be Future[Either[CreateDataError, Data]] and your first case would be
// Having somewhere decalred
trait CreateDataError
object DataAlreadyExistsError
//...
//...
case Some(data) =>
Future.successful(Left(DataAlreadyExistsError))

Using extractors to parse text files

I'm trying to improve a CSV parsing routine and feel that extractors could be useful here but can't figure them out. Suppose there's a file with user ids and emails:
1,alice#alice.com
2,bob#bob.com
3,carol#carol.com
If the User class is defined as case class User(id: Int, email: String) everything is pretty easy with something like
lines map { line =>
line split "," match {
case Array(id, email) => User(id.toInt, email)
}
}
What I don't understand is how to deal with the case where User class can have complex properties e.g
case class Email(username: String, host: string)
case class User(id: Int, email: Email)
You probably want to use a regular expression to extract the contents of the email address. Maybe something like this:
val lines = Vector(
"1,alice#alice.com",
"2,bob#bob.com",
"3,carol#carol.com")
case class Email(username: String, host: String)
case class User(id: Int, email: Email)
val EmailRe = """(.*)#(.*\.com)""".r // substitute a real email address regex here
lines.map { line =>
line.split(",") match {
case Array(id, EmailRe(uname, host)) => User(id.toInt, Email(uname, host))
}
}
Here you go, an example using a custom Extractor.
// 1,Alice,21212,Baltimore,MD" -> User(1, Alice, Address(21212, Baltimore, MD))
Define a custom Extractor that creates the objects out of given String:
object UserExtractor {
def unapply(s: String) : Option[User] = try {
Some( User(s) )
}
catch {
// bettor handling of bad cases
case e: Throwable => None
}
}
Case classes to hold the data with a custom apply on Comapnion object on User:
case class Address(code: String, cit: String, county: String)
case class User(id: Int, name: String, address: Address)
object User {
def apply(s: String) : User = s.split(",") match {
case Array(id, name, code, city, county) => User(id.toInt, name, Address(code, city, county) )
}
}
Unapplying on a valid string (in the example valid means the correct number of fields).
"1,Alice,21212,Baltimore,MD" match { case UserExtractor(u) => u }
res0: User = User(1,Alice,Address(21212,Baltimore,MD))
More tests could be added with more custom apply methods.
I'd use a single RegexExtractor :
val lines = List(
"1,alice#alice.com",
"2,bob#bob.com",
"3,carol#carol.com"
)
case class Email(username: String, host: String)
case class User(id: Int, email: Email)
val idAndEMail = """^([^,]+),([^#]+)#(.+)$""".r
and define a function that transforms a line to the an User :
def lineToUserWithMail(line: String) : Option[User] =
idAndEMail.findFirstMatchIn(line) map {
case userWithEmail(id,user,host) => User(id.toInt, Email(user,host) )
}
Applying the function to all lines
lines flatMap lineToUserWithMail
//res29: List[User] = List(User(1,Email(alice,alice.com)), User(2,Email(bob,bob.com)), User(3,Email(carol,carol.com)))
Alternatively you could implement custom Extractors on the case classe by adding an unnapply Method. But for that case it wouldn't be worth the pain.
Here is an example for unapply
class Email(user:String, host:String)
object Email {
def unapply(s: String) : Option[(String,String)] = s.split("#") match {
case Array(user, host) => Some( (user,host) )
case _ => None
}
}
"bob#bob.com" match {
case Email(u,h) => println( s"$u , $h" )
}
// prints bob , bob.com
A word of warning on using Regex to parse CSV-data. It's not as easy as you might think, i would recommend to use a CSV-Reader as http://supercsv.sourceforge.net/ which handles some nasty edge cases out of the box.

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)