I am having trouble calling out to another function which returns a Future from within a Future.traverse. The scenario is that I have a Seq[Document] from which I need to turn them into a Future[Seq[NewsArticle]]. However, in order to do this I need to take the doc.categoryID and use it to call out to another API to build the NewsCategory, which will be returned as a Future[NewsCategory]. My problem is not knowing how to squish this in to the Future.traverse after it has returned.
Here is my scala code so far:
object NewsArticle {
def buildNewsArticles(docs:Seq[Document]):Future[Seq[NewsArticle]] = {
Future.traverse(docs) { doc =>
val categoryID = doc.catID
val title = doc.title
val pdf = doc.pdfLink
val image = doc.imageUrl
val body = doc.body
//need to call NewsCategory.buildNewsCategory(categoryID)
//but it returns a Future[NewsCategory]
//can't use for-yield because it still only yields
//a Future
future(NewsArticle( ??? ,title, pdf, image, body)
}
}
}
object NewsCategory {
def buildNewsCategory(catID:String):Future[NewsCategory] = {
// Calls external api across the wire and
// Returns a Future[NewsCategory]
}
}
// CASE CLASSES
case class NewsArticle(
category:NewsCategory,
title:String,
pdf:String,
image:String,
body:String)
case class NewsCategory(
id:String,
name:String
description:String)
Thanks for helping
It sounds like you want to create a NewsArticle instance based on the NewsCategory returned in the future from buildNewsCategory, which means that you were on the right track. I think the following will do what you want:
Future.traverse(docs) { doc =>
val categoryID = doc.catID
val title = doc.title
val pdf = doc.pdfLink
val image = doc.imageUrl
val body = doc.body
// This could be a for-yield instead as you note.
NewsCategory.buildNewsCategory(categoryID).map { category =>
NewsArticle(category, title, pdf, image, body)
}
}
Maybe try this:
object NewsArticle {
def buildNewsArticles(docs:Seq[Document]):Future[Seq[NewsArticle]] = {
Future.traverse(docs) { doc =>
val categoryID = doc.catID
val title = doc.title
val pdf = doc.pdfLink
val image = doc.imageUrl
val body = doc.body
for {
cat <- NewsCategory.buildNewsCategory(categoryID)
} yield new NewsArticle(cat, title, pdf, image, body)
}
}
}
Related
I need to access this data mapData from Calculation.scala. The way I call the function in Final.scala is as seen below.
When I see the output of fetch_data() or print it I see Future(< not completed >) and result is empty. I do not know how to wait until all data is downloaded and then access mapData? MAy I know how to do it? I am new to scala. In C++ I am aware of callbacks and handling is easy there. But in scala I a using Future, Await or OnComplete, but not clear how to do it.
Final.Scala
object finalComputation {
val calculationInfo = new Calculaton()
calclulationInfo.fetch_data()
val result = calculationInfo.getMapData()
def main(args: Array[String]): Unit = {
........
}
}
Calculation.scala
class Calculation {
var mapData = Map.empty[String, String]
def createMapData(metricItem: ActualMetrics) = {
mapData += (metricItem._1 -> metricItem._2)
}
def getMapData() = {
mapData
}
def fetch_data() = {
val totalData: Future[Done] =
querApi
.getData()
.map { data =>
(data)
}
}
}
Await.result(totalData, Duration.Inf).runForeach(unit => {
createMapData(parse.From(totalData))
})
}
}
Well, by starter don't mix concurrency with mutability.
Second, don't create imperative APIs that require some specific call order.
Third, Future is just a fancy wrapper over callbacks.
And fourth, don't initiate computations before the main
// file: Calculation.scala
class Calculation(queryApi: Api) {
def fetchData(): Future[Map[String, String]] =
querApi.getData().map { data =>
data.view.map { metric =>
val ActualMetrics(key, value) = parse(metric)
key -> value
}.toMap
}
}
// file: Main.scala
object Main {
def main(args: Array[String]): Unit = {
val calculation = new Calculation(...)
val dataF = calculation.fetchData()
val result = dataF.map { data =>
// Here you can process the fetched data
// It may be flatMap or foreach, instead of map;
// depending on what you want to do, check the Scaladoc
}
// I don't use future, but I think here you need to do a final await of result.
// In order to avoid the program to finish before the async computation.
}
}
I had to assume some types, but I hope this gives you a general idea of what to do.
My advice, pick any Scala course / book / tutorial and properly learn the language.
This method uses Slick 3.1.x and it returns correctly an object of type Future[List[Analysis]] however the list is always empty. Why is that and how to fix this?
def readMany = {
val db = Database.forConfig("db1")
var list = new ListBuffer[Analysis]()
try {
val query = TableQuery[AnalysisDB]
val action = query.sortBy(_.name).result
val future = db.run(action).map(_.foreach {
case (analysis) => list += Analysis ( analysis.sk, analysis.name )
})
Future { list.toList }
} finally db.close
}
The list is being returned before it is populated. Your val future (database call) is being ignored/discarded, and a Future(list) (still empty) returned. Try this;
def readMany = {
val db = Database.forConfig("db1")
try {
val query = TableQuery[AnalysisDB]
val action = query.sortBy(_.name).result
db.run(action).map(_.map {
a => Analysis(a.sk, a.name)
})
} finally db.close
}
This also avoids the smelly mutable ListBuffer.
I have a uuid generator, like:
class NewUuid {
def apply: String = UUID.randomUUID().toString.replace("-", "")
}
And other class can use it:
class Dialog {
val newUuid = new NewUuid
def newButtons(): Seq[Button] = Seq(new Button(newUuid()), new Button(newUuid()))
}
Now I want to test the Dialog and mock the newUuid:
val dialog = new Dialog {
val newUuid = mock[NewUuid]
newUuid.apply returns "uuid1"
}
dialog.newButtons().map(_.text) === Seq("uuid1", "uuid1")
You can see the returned uuid is always uuid1.
Is it possible to let newUuid to return different values for different calls? e.g. The first call returns uuid1, the second returns uuid2, etc
newUuid.apply returns "uudi1" thenReturns "uuid2"
https://etorreborre.github.io/specs2/guide/SPECS2-3.5/org.specs2.guide.UseMockito.html
Use an iterator to produce your UUIDs
def newUuid() = UUID.randomUUID().toString.replace("-", "")
val defaultUuidSource = Iterator continually newUuid()
class Dialog(uuids: Iterator[String] = defaultUuidSource) {
def newButtons() = Seq(
Button(uuids.next()),
Button(uuids.next())
)
}
Then provide a different one for testing:
val testUuidSource = Iterator from 1 map {"uuid" + _}
new Dialog(testUuidSource)
I am loading google map in async way ,
#JSExport("sample")
object Sample {
def loadScript = {
val script = document.createElement("script").asInstanceOf[HTMLScriptElement]
script.`type` = "text/javascript"
//case 1
script.src = "https://maps.googleapis.com/maps/api/js?v=3.exp&callback=sample().initialize"
// case 2
script.src = "https://maps.googleapis.com/maps/api/js?v=3.exp&callback=sample.initialize"
document.body.appendChild(script)
}
#JSExport
def initialize() :Unit = {
println(" map loaded successfully")
}
}
In case 1 google sending response - 400(bad request)
In case 2 i am getting undefined function ( window.sample.initialize())
i can define a javascript function ,inside that function i can call sample().initialize() , but is there any cleaner way ?
I would use Scala.js' dynamic API to create the JavaScript function on the top-level. The advantage over #gzm0's solution is that it's less hacky, and requires less boilerplate.
object Sample {
def loadScript = {
val script = document.createElement("script").asInstanceOf[HTMLScriptElement]
script.`type` = "text/javascript"
script.src = "https://maps.googleapis.com/maps/api/js?v=3.exp&callback=initializeSample"
document.body.appendChild(script)
js.Dynamic.global.initializeSample = initialize _
}
private def initialize(): Unit =
println("map loaded successfully")
}
This is a hacky answer, but potentially useful as a workaround.
Instead of giving the Google API something corresponding to a Scala.js function, you can give it the module initializer directly:
object Sample {
def loadScript = {
val script = document.createElement("script").asInstanceOf[HTMLScriptElement]
script.`type` = "text/javascript"
script.src = "https://maps.googleapis.com/maps/api/js?v=3.exp&callback=initializeSample"
document.body.appendChild(script)
}
}
#JSExport("initializeSample")
object Initializer {
println(" map loaded successfully")
}
I am making a simple snippet that should pass a Box[String] with the requests user-agent to a helper class that passes back the css classes that should be added to the html element. I am doing this since it seems tricky to get Lift to supply a html respons with conditional comments like those in html5boilerplate. This is what I have now and it works:
class LiftBoilerplate {
def render = "html [class+]" #> getClassForUserAgent(S.request)
private def getClassForUserAgent(request:Box[Req]) = request match {
case Full(r) => LiftBoilerplateHelper.getHtmlClass(r.userAgent)
case _ => ""
}
}
My problem is that I'd like to write a unit test for this like:
object LiftBoilerplateSpecs extends Specification {
val session = new LiftSession("", randomString(20), Empty)
"LiftBoilerplate" should {
"add 'no-js' to the class of an html tag element" in {
val snippet = new LiftBoilerplate
val result = snippet.render(<html><head></head><body>test</body></html>)
result must ==/(<html class="no-js"><head></head><body>test</body></html>)
}
}
}
This test fails since S.request is Empty. What should I do to supply the snippet with a mocked request with a userAgent in it?
So far I have looked at http://www.assembla.com/spaces/liftweb/wiki/Unit_Testing_Snippets_With_A_Logged_In_User
and
http://www.assembla.com/spaces/liftweb/wiki/Mocking_HTTP_Requests
but I do not understand how to achive my goal.
To make the request and apply it automatically in each test example you will need to use the Trait AroundExample to wrap each test in a S.init call:
object LiftBoilerplateSpecs extends Specification with AroundExample {
val session = new LiftSession("", randomString(20), Empty)
def makeReq = {
val mockRequest = new MockHttpServletRequest("http://localhost")
mockRequest.headers = Map("User-Agent" -> List("Safari"))
new Req(Req.NilPath, "", GetRequest, Empty, new HTTPRequestServlet(mockRequest, null),
System.nanoTime, System.nanoTime, false,
() => ParamCalcInfo(Nil, Map(), Nil, Empty), Map())
}
def around[T <% Result](t: => T) = S.init(makeReq, session)(t)
"LiftBoilerplate" should {
"add 'no-js' to the class of an html tag element" in {
val snippet = new LiftBoilerplate
val result = snippet.render(<html><head></head><body>test</body></html>)
result must ==/(<html class="no-js"><head></head><body>test</body></html>)
}
}
}