I have the following code:
def getContentComponents: Action[AnyContent] = Action.async {
val test = contentComponentDTO.list().map { contentComponentsFuture =>
contentComponentsFuture.foreach(contentComponentFuture =>
contentComponentFuture.typeOf match {
case 1 =>
println("blubb")
case 5 =>
contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(
text => {
contentComponentFuture.text = text.text
println(text.text)
println(contentComponentFuture.text)
}
)
}
)
}
Future.successful(Ok(Json.obj("contentComponents" -> test)))
}
and I got this error message:
The .list() method should return a Future[ContentComponentModel]
def list(): Future[Seq[ContentComponentModel]] = db.run {
whats my mistake in this case?
thanks
Your contentComponentsFuture should be of type Seq[ContentComponentModel]. In this case You should move
Future.successful(Ok(Json.obj("contentComponents" -> test)))
just into the map expression (which is async) after loop.
It should looks something like:
def getContentComponents: Action[AnyContent] = Action.async {
val test = contentComponentDTO.list().map { contentComponents =>
contentComponents.foreach(contentComponentFuture =>
contentComponentFuture.typeOf match {
case 1 =>
println("blubb")
case 5 =>
contentComponentDTO.getContentComponentText(contentComponentFuture.id.get).map(
text => {
contentComponentFuture.text = text.text
println(text.text)
println(contentComponentFuture.text)
}
)
}
)
Future.successful(Ok(Json.obj("contentComponents" -> contentComponents)))
}
}
I tried in this way but it always go to case Nil.
def findByLastName(lastName:String)=Action.async {
val cursor = Json.obj("lastName" -> lastName)
StudentDaoAndEntity.findAllStudent(cursor) flatMap { lastName =>
ExaminationDao.findStudent(cursor) flatMap { lastName =>
LibraryDao.findStudent(cursor) map {
{
case Nil => Ok("Student Not Found")
case l: Seq[JsObject] => Ok(Json.toJson(l))
}
}
}
}
}
And my defined functions in Database are:
In StudentDaoAndEntity:
def findAllStudent(allStd: JsObject): Future[Seq[JsObject]] = {
// gather all the JsObjects in a list
collection.find(allStd).cursor[JsObject].collect[List]()
}
In LibraryDao:
def findStudent(allStd: JsObject): Future[Seq[JsObject]] = {
// gather all the JsObjects in a list
collection.find(allStd).cursor[JsObject].collect[List]()
}
In ExaminationDao:
def findStudent(allStd: JsObject): Future[Seq[JsObject]] = {
// gather all the JsObjects in a list
collection.find(allStd).cursor[JsObject].collect[List]()
}
The issue is with shadowing the lastName argument inside each lambda (as comments already pointed out). That being said, you can use a for-comprehension to make your code more readable
def findByLastName(lastName:String) = Action.async {
val cursor = Json.obj("lastName" -> lastName)
for {
_ <- StudentDaoAndEntity.findAllStudent(cursor)
_ <- ExaminationDao.findStudent(cursor)
students <- LibraryDao.findStudent(cursor)
} yield students match {
case Nil => Ok("Student Not Found")
case l: Seq[JsObject] => Ok(Json.toJson(l))
}
}
This is what I want:
def findByLastName(lastName:String)=Action.async {
request =>
val cursor = Json.obj("lastName" -> lastName)
StudentDaoAndEntity.findAllStudent(cursor) flatMap {
student =>
ExaminationDao.findStudent(cursor) flatMap {
examination =>
LibraryDao.findStudent(cursor) map {
{
case Nil => Ok("Student Not Found")
case library: Seq[JsObject] =>
val finalResult = student ++ examination ++ library
Ok(JsArray(finalResult))
}
}
}
}
}
I want to insert a new row with different status value if that row exists and return the new instance as Some to the caller. If it does not exist, nothing happens and return None. What I have is
def revoke(name: String, issueFor: String): Future[MyLicense] = {
val retrieveLicense = slickLicenses.filter(l => l.name === name && l.issueFor === issueFor).
sortBy(_.modifiedOn.desc).result.headOption
val insertLicense = for {
licenseOption <- retrieveLicense
} yield {
licenseOption match {
case Some(l) => Some(slickLicenses += l.copy(status = RevokedLicense))
case None => None
}
}
db.run(insertLicense).map {r =>
r.map { dbLicense => MyLicense(dbLicense.name, dbLicense.status)
}
}
How do I fix this?
Edit 1
I changed the code to
override def revoke(name: String, issueFor: String): Future[String] = {
val retrieveLicense = slickLicenses.filter(l => l.name === name && l.issueFor === issueFor).
sortBy(_.modifiedOn.desc).result.headOption
val insertLicenseAction = for {
l <- retrieveLicense
newLicense <- slickLicenses += l.get.copy(status = RevokedLicense)
} yield newLicense
db.run(insertLicenseAction).map(_ => name)
}
Hence, the question changes to how do I get the instance I insert into the database? Thanks
This is what I did finally,
override def revoke(name: String, issueFor: String): Future[String] = {
val retrieveLicense = slickLicenses.filter(l => l.name === name && l.issueFor === issueFor).
sortBy(_.modifiedOn.desc).result.headOption
val insertLicenseAction = for {
l <- retrieveLicense
_ <- l.map(x => slickLicenses += x.copy(status = RevokedLicense,
modifiedOn = new Timestamp(System.currentTimeMillis())))
.getOrElse(DBIO.successful(l))
} yield ()
db.run(insertLicenseAction).map(x => name)}
Sorry about the title, please edit it to be more descriptive if you can!
Is there a way to generalize this with scala? I have quite a few fields that can be filtered against, and this is just plain ugly! The problem I ran against was matching parameter name against the case class field, can it be done in a more general way, without this much code duplication?
get("/MostClicked") { request =>
val res = MongoDbOps.findMostClicked()
val res1 = request.params.get("source") match {
case None => res
case Some(f) => res.filter(_.source == f)
}
val res2 = request.params.get("category") match {
case None => res1
case Some(f) => res1.filter(_.category == f)
}
// more of the same...
render.plain {
res2.toJson.prettyPrint
}.toFuture
}
You can try either of the two approaches below.
case class MostClicked(
source: String,
category: String,
rating: String)
object MongoDbOps {
def findMostClicked() = List[MostClicked]()
}
class Request {
val params = Map[String, String]()
}
def get(path: String)(f: Request => String) = {
f(new Request)
}
First is to use a List of matchers and then apply them sequentially using foldLeft:
get("/MostClicked") { request =>
val res = MongoDbOps.findMostClicked()
val kfun = List(
"source" -> ((x: MostClicked, y: String) => x.source == y),
"category" -> ((x: MostClicked, y: String) => x.category == y),
"rating" -> ((x: MostClicked, y: String) => x.rating == y))
val r = kfun.foldLeft(res) { (x, y) =>
request.params.get(y._1)
.map(f => res.filter(y._2(_, f)))
.getOrElse(x)
}
r.toString
// more of the same...
render.plain {
r.toJson.prettyPrint
}.toFuture
}
Or simply make it more readable:
get("/MostClicked") { request =>
val res = MongoDbOps.findMostClicked()
val res1 = request.params.get("source")
.map(f => res.filter(_.source == f))
.getOrElse(res)
val res2 = request.params.get("category")
.map(f => res.filter(_.category == f))
.getOrElse(res1)
val res3 = request.params.get("rating")
.map(f => res.filter(_.rating == f))
.getOrElse(res2)
// more of the same...
render.plain {
res3.toJson.prettyPrint
}.toFuture
}
I had to break it down to parts and work out the types, thus the smaller methods.
It works in my simple experiment and gets rid of the duplication where it can, but might not be as readable?
Note that unless you get into reflection, you still need to create the name of the parameter and how it should be filtered.
This looks to be the same as tuxdna's answer except with types to increase readability and maintainability
SETUP
case class Request(params: Map[String, String])
case class Result(category: String, source: String)
type Filterer = (Result, String) => Boolean
case class FilterInfo(paramName: String, filterer: Filterer)
type Analyzer = FilterInfo => List[Result]
val request = Request(Map("source"->"b"))
EXTRACTION METHODS
def reduce(filterInfos: List[FilterInfo], results: List[Result]) = {
filterInfos.foldLeft(results) { (currentResult, filterInfo) =>
request.params.get(filterInfo.paramName)
.map(filterVal => currentResult.filter(filterInfo.filterer(_, filterVal)))
.getOrElse(currentResult)
}
}
USAGE
val filterInfos = List(
FilterInfo("source", (result, filterVal) => result.source == filterVal),
FilterInfo("category", (result, filterVal) => result.category == filterVal))
val res = List(Result("a","a"), Result("b", "b"))
reduce(filterInfos, res)
Used in your example it would be more like this:
get("/MostClicked") { request =>
val res = MongoDbOps.findMostClicked()
val filterInfos = List(
FilterInfo("source", (result, filterVal) => result.source == filterVal),
FilterInfo("category", (result, filterVal) => result.category == filterVal))
val finalResult = reduce(filterInfos, res)
render.plain {
finalResult.toJson.prettyPrint
}.toFuture
}
I have multiple Option's. I want to check if they hold a value. If an Option is None, I want to reply to user about this. Else proceed.
This is what I have done:
val name:Option[String]
val email:Option[String]
val pass:Option[String]
val i = List(name,email,pass).find(x => x match{
case None => true
case _ => false
})
i match{
case Some(x) => Ok("Bad Request")
case None => {
//move forward
}
}
Above I can replace find with contains, but this is a very dirty way. How can I make it elegant and monadic?
Edit: I would also like to know what element was None.
Another way is as a for-comprehension:
val outcome = for {
nm <- name
em <- email
pwd <- pass
result = doSomething(nm, em, pwd) // where def doSomething(name: String, email: String, password: String): ResultType = ???
} yield (result)
This will generate outcome as a Some(result), which you can interrogate in various ways (all the methods available to the collections classes: map, filter, foreach, etc.). Eg:
outcome.map(Ok(result)).orElse(Ok("Bad Request"))
val ok = Seq(name, email, pass).forall(_.isDefined)
If you want to reuse the code, you can do
def allFieldValueProvided(fields: Option[_]*): Boolean = fields.forall(_.isDefined)
If you want to know all the missing values then you can find all missing values and if there is none, then you are good to go.
def findMissingValues(v: (String, Option[_])*) = v.collect {
case (name, None) => name
}
val missingValues = findMissingValues(("name1", option1), ("name2", option2), ...)
if(missingValues.isEmpty) {
Ok(...)
} else {
BadRequest("Missing values for " + missingValues.mkString(", ")))
}
val response = for {
n <- name
e <- email
p <- pass
} yield {
/* do something with n, e, p */
}
response getOrElse { /* bad request /* }
Or, with Scalaz:
val response = (name |#| email |#| pass) { (n, e, p) =>
/* do something with n, e, p */
}
response getOrElse { /* bad request /* }
if ((name :: email :: pass :: Nil) forall(!_.isEmpty)) {
} else {
// bad request
}
I think the most straightforward way would be this:
(name,email,pass) match {
case ((Some(name), Some(email), Some(pass)) => // proceed
case _ => // Bad request
}
A version with stone knives and bear skins:
import util._
object Test extends App {
val zero: Either[List[Int], Tuple3[String,String,String]] = Right((null,null,null))
def verify(fields: List[Option[String]]) = {
(zero /: fields.zipWithIndex) { (acc, v) => v match {
case (Some(s), i) => acc match {
case Left(_) => acc
case Right(t) =>
val u = i match {
case 0 => t copy (_1 = s)
case 1 => t copy (_2 = s)
case 2 => t copy (_3 = s)
}
Right(u)
}
case (None, i) =>
val fails = acc match {
case Left(f) => f
case Right(_) => Nil
}
Left(i :: fails)
}
}
}
def consume(name: String, email: String, pass: String) = Console println s"$name/$email/$pass"
def fail(is: List[Int]) = is map List("name","email","pass") foreach (Console println "Missing: " + _)
val name:Option[String] = Some("Bob")
val email:Option[String]= None
val pass:Option[String] = Some("boB")
val res = verify(List(name,email,pass))
res.fold(fail, (consume _).tupled)
val res2 = verify(List(name, Some("bob#bob.org"),pass))
res2.fold(fail, (consume _).tupled)
}
The same thing, using reflection to generalize the tuple copy.
The downside is that you must tell it what tuple to expect back. In this form, reflection is like one of those Stone Age advances that were so magical they trended on twitter for ten thousand years.
def verify[A <: Product](fields: List[Option[String]]) = {
import scala.reflect.runtime._
import universe._
val MaxTupleArity = 22
def tuple = {
require (fields.length <= MaxTupleArity)
val n = fields.length
val tupleN = typeOf[Tuple2[_,_]].typeSymbol.owner.typeSignature member TypeName(s"Tuple$n")
val init = tupleN.typeSignature member nme.CONSTRUCTOR
val ctor = currentMirror reflectClass tupleN.asClass reflectConstructor init.asMethod
val vs = Seq.fill(n)(null.asInstanceOf[String])
ctor(vs: _*).asInstanceOf[Product]
}
def zero: Either[List[Int], Product] = Right(tuple)
def nextProduct(p: Product, i: Int, s: String) = {
val im = currentMirror reflect p
val ts = im.symbol.typeSignature
val copy = (ts member TermName("copy")).asMethod
val args = copy.paramss.flatten map { x =>
val name = TermName(s"_$i")
if (x.name == name) s
else (im reflectMethod (ts member x.name).asMethod)()
}
(im reflectMethod copy)(args: _*).asInstanceOf[Product]
}
(zero /: fields.zipWithIndex) { (acc, v) => v match {
case (Some(s), i) => acc match {
case Left(_) => acc
case Right(t) => Right(nextProduct(t, i + 1, s))
}
case (None, i) =>
val fails = acc match {
case Left(f) => f
case Right(_) => Nil
}
Left(i :: fails)
}
}.asInstanceOf[Either[List[Int], A]]
}
def consume(name: String, email: String, pass: String) = Console println s"$name/$email/$pass"
def fail(is: List[Int]) = is map List("name","email","pass") foreach (Console println "Missing: " + _)
val name:Option[String] = Some("Bob")
val email:Option[String]= None
val pass:Option[String] = Some("boB")
type T3 = Tuple3[String,String,String]
val res = verify[T3](List(name,email,pass))
res.fold(fail, (consume _).tupled)
val res2 = verify[T3](List(name, Some("bob#bob.org"),pass))
res2.fold(fail, (consume _).tupled)
I know this doesn't scale well, but would this suffice?
(name, email, pass) match {
case (None, _, _) => "name"
case (_, None, _) => "email"
case (_, _, None) => "pass"
case _ => "Nothing to see here"
}