Play reactivemongo doesnt stop - mongodb

In my play/swagger/reactivemongo application i use the following function in the controller to get a list of results with "EntityID" 8.
#ApiOperation(value = "Gets the item of a specific ID", notes = "Returns an Item", responseClass = "Item", httpMethod = "GET")
#ApiErrors(Array(
new ApiError(code = 400, reason = "Invalid ID supplied"),
new ApiError(code = 404, reason = "Item not found")))
def index = Action { implicit request =>
Async {
// test id
var myVar: Int = 8
val cursor: Cursor[JsObject] = collection.find(Json.obj("EntityID" -> myVar))
.sort(Json.obj("_id" -> -1))
.cursor[JsObject]
// gather all the JsObjects in a list
val futureUsersList: Future[List[JsObject]] = cursor.toList
// transform the list into a JsArray
val futureUsersListJs: Future[JsArray] = futureUsersList.map { Measurements_live =>
Json.arr(Measurements_live)
}
futureUsersListJs.map { Measurements_live =>
Ok(Measurements_live)
}
}
}
The measurements model:
case class Measurements_live(
EntityID: Int,
SensorID: Int,
Datetime: Date,
Value: String)
object JsonFormats {
import play.api.libs.json.Json
import play.api.data._
import play.api.data.Forms._
implicit val measureFormat = Json.format[Measurements_live]
val measureForm = Form(
mapping(
"EntityID" -> number,
"SensorID" -> number,
"Datetime" -> date,
"Value" -> text)(Measurements_live.apply _)(Measurements_live.unapply _))
}
The problems is that it wont stop loading. There are a total of 35000 objects in the database.
I've played with cursor.close() that stops the cursor and that will return some results. What i want is that the cursor automatically closes when all results are returned.

My first instinct is "that's a lot of objects to be fetching in one HTTP request!".
I can't say exactly why this doesn't work but I suspect it's the collating into a Future[List[JsObject]] that is killing the thing.
I would use the version of Cursor.toList() that takes an upTo parameter, set that to a reasonable number (say 100) and then allow the client to specify which page of results they want to see as a parameter. You could even let the client set the page size if you trust them not to get too greedy.
Obviously you'll need to add another clause to your find to get the correct offset for the pagination; if your client just tells you the highest _id value that they've already got, your find() query would still be very simple.

Related

Scala : How to pass a class field into a method

I'm new to Scala and attempting to do some data analysis.
I have a CSV files with a few headers - lets say item no., item type, month, items sold.
I have made an Item class with the fields of the headers.
I split the CSV into a list with each iteration of the list being a row of the CSV file being represented by the Item class.
I am attempting to make a method that will create maps based off of the parameter I send in. For example if I want to group the items sold by month, or by item type. However I am struggling to send the Item.field into a method.
F.e what I am attempting is something like:
makemaps(Item.month);
makemaps(Item.itemtype);
def makemaps(Item.field):
if (item.field==Item.month){}
else (if item.field==Item.itemType){}
However my logic for this appears to be wrong. Any ideas?
def makeMap[T](items: Iterable[Item])(extractKey: Item => T): Map[T, Iterable[Item]] =
items.groupBy(extractKey)
So given this example Item class:
case class Item(month: String, itemType: String, quantity: Int, description: String)
You could have (I believe the type ascriptions are mandatory):
val byMonth = makeMap[String](items)(_.month)
val byType = makeMap[String](items)(_.itemType)
val byQuantity = makeMap[Int](items)(_.quantity)
val byDescription = makeMap[String](items)(_.description)
Note that _.month, for instance, creates a function taking an Item which results in the String contained in the month field (simplifying a little).
You could, if so inclined, save the functions used for extracting keys in the companion object:
object Item {
val month: Item => String = _.month
val itemType: Item => String = _.itemType
val quantity: Item => Int = _.quantity
val description: Item => String = _.description
// Allows us to determine if using a predefined extractor or using an ad hoc one
val extractors: Set[Item => Any] = Set(month, itemType, quantity, description)
}
Then you can pass those around like so:
val byMonth = makeMap[String](items)(Item.month)
The only real change semantically is that you explicitly avoid possible extra construction of lambdas at runtime, at the cost of having the lambdas stick around in memory the whole time. A fringe benefit is that you might be able to cache the maps by extractor if you're sure that the source Items never change: for lambdas, equality is reference equality. This might be particularly useful if you have some class representing the collection of Items as opposed to just using a standard collection, like so:
object Items {
def makeMap[T](items: Iterable[Item])(extractKey: Item => T): Map[T,
Iterable[Item]] =
items.groupBy(extractKey)
}
class Items(val underlying: immutable.Seq[Item]) {
def makeMap[T](extractKey: Item => T): Map[T, Iterable[Item]] =
if (Item.extractors.contains(extractKey)) {
if (extractKey == Item.month) groupedByMonth.asInstanceOf[Map[T, Iterable[Item]]]
else if (extractKey == Item.itemType) groupedByItemType.asInstanceOf[Map[T, Iterable[Item]]]
else if (extractKey == Item.quantity) groupedByQuantity.asInstanceOf[Map[T, Iterable[Item]]]
else if (extractKey == Item.description) groupedByDescription.asInstanceOf[Map[T, Iterable[Item]]]
else throw new AssertionError("Shouldn't happen!")
} else {
Items.makeMap(underlying)(extractKey)
}
lazy val groupedByMonth = Items.makeMap[String](underlying)(Item.month)
lazy val groupedByItemType = Items.makeMap[String](underlying)(Item.itemType)
lazy val groupedByQuantity = Items.makeMap[Int](underlying)(Item.quantity)
lazy val groupedByDescription = Items.makeMap[String](underlying)(Item.description)
}
(that is almost certainly a personal record for asInstanceOfs in a small block of code... I'm not sure if I should be proud or ashamed of this snippet)

Modify Future[JsArray] in Scala

I use the Play 2.0 framework for getting data from my MongoDB.
This is done by the following code:
def getTopicsInAppFormat = Action.async {
// let's do our query
val cursor: Cursor[TopicModel] = topicCollection.find(Json.obj()).cursor[TopicModel]
// gather all the JsObjects in a list
val futureTopicsList: Future[List[TopicModel]] = cursor.collect[List]()
// transform the list into a JsArray
val futurePersonsJsonArray: Future[JsArray] = futureTopicsList.map { topics =>
Json.arr(topics)
}
// everything's ok! Let's reply with the array
futurePersonsJsonArray.map {
topics =>
Ok(topics(0))
}
}
But the problem is that I want the function to return an alternative representation of the data. So, I want for example to change the amount of attributes, etc. What is a good way to achieve that? I tried to modify the data (respectively create a new array with the new format) in the last step, right before the Ok() function. However, I didn't have any progress with that :/
EDIT:
At the moment I'm trying to create new JSON objects but I'm stuck while getting the data from the original one...
My current code looks like this:
futurePersonsJsonArray.map {
topics =>
/* Access a attribute */
println(topics(0).\("content"))
/* However: error: JsUndefined('content' is undefined on object */
/* Could be used to set the new attributes */
val myNewJson = Json.obj(
"name" -> "JohnDoe",
"age" -> "123",
"created" -> new java.util.Date().getTime())
Ok(/*topics(0)*/myNewJson)
}
You're probably misinterpreting the format of topics. The message you're getting is telling you that there is no content property on the first element of the topics array. This is a simplified example:
val myObject = Json.obj("a" -> 1, "b" -> 2)
myObject \ "a" //JsNumber(1)
myObject \ "c" //JsUndefined('c' is undefined on object...)
This makes sense, since we get undefined in Javascript when trying to read a property that doesn't exist. In the Play Json library, \ always returns a JsValue, and one of its subtypes is JsUndefined.
You should reexamine the format of the objects in the topics array and reassess how you can access its values.
Now, my final solution for the problem "modify the data just before returning" looks like this:
futurePersonsJsonArray.map {
topics =>
def createNewFormat(uID: JsValue, name: JsValue, content: JsValue, lat: JsValue, lng: JsValue): JsObject = {
return Json.obj(
"id" -> uID,
[...]
}
/* Access the needed attributes */
val uIDs = topics(0).\\("uID")
val names = topics(0).\\("name")
val content = topics(0).\\("content")
val gps = topics(0).\\("gps")
var dataArray = new JsArray()
/* create new Array */
for( i <- 0 to uIDs.length-1){
dataArray = dataArray.append(createNewFormat(uIDs(i),names(i),content(i),gps(i)(0),gps(i)(1)))
}
/* put this into a new JSObject */
var returnThis = Json.obj("data" -> dataArray)
Ok(returnThis)
}
Maybe this does help someone with similar problems :)

How to access my forms properties when validation fails in the fold call?

I have an action where my form posts too like:
def update() = Action { implicit request =>
userForm.bindFromRequest().fold(
errorForm => {
if(errorForm.get.isDefined) {
val id = errorForm.get.id // runtime error during form post
}
BadRequest(views.html.users.update(errorForm))
},
form => {
val id = form.id // works fine!
Ok("this works fine" + form.name)
}
)
}
When I do the above, I get an error:
[NoSuchElementException: None.get]
If there are no validation errors, the form post works just fine in the 'success' part of the fold call.
I need to get the id value in the error section, how can I do that?
When a Form fails to bind to a model, all you will have available is the data in the form of a Map[String, String] and the validation errors (essentially). So you can access the values as Strings, but keep in mind they also might not be available.
For example:
case class Test(id: Int, name: String)
val testForm = Form {
mapping(
"id" -> number,
"name" -> nonEmptyText
)(Test.apply)(Test.unapply)
}
Now try to bind to this Form with a missing name field:
scala> val errorForm = testForm.bind(Map("id" -> "1"))
errorForm: play.api.data.Form[Test] = Form(ObjectMapping2(<function2>,<function1>,(id,FieldMapping(,List())),(name,FieldMapping(,List(Constraint(Some(constraint.required),WrappedArray())))),,List()),Map(id -> 1),List(FormError(name,List(error.required),List())),None)
You can access individual fields using apply. And each Field has a value method with returns Option[String].
scala> errorForm("name").value
res4: Option[String] = None
scala> errorForm("id").value
res5: Option[String] = Some(1)
Possible usage:
errorForm("name").value map { name =>
println("There was an error, but name = " + name)
} getOrElse {
println("name field is missing")
}
Keep in mind that the non-bound data is only a String, so more complicated data structures may be harder to access, and most of the time it will not be type safe.
Another way is to access the raw Map directly:
scala> errorForm.data
res6: Map[String,String] = Map(id -> 1)

Building a document functionally and based on input value in a Play 2 controller in Scala and ReactiveMongo

I've a Play controller Action that edits a document in MongoDB using ReactiveMongo. The code is shown below. Both name and keywords are optional. I'm creating a temp BSONDocument() and adding tuples to it based on if name and keywords exist are not empty. However, tmp is currently mutable(is a var). I'm wondering how I can get rid of the var.
def editEntity(id: String, name: Option[String], keywords: Option[String]) = Action {
val objectId = new BSONObjectID(id)
//TODO get rid of var here
var tmp = BSONDocument()
if (name.exists(_.trim.nonEmpty)) {
tmp = tmp.add(("name" -> BSONString(name.get)))
}
val typedKeywords : Option[List[String]] = Utils.getKeywords(keywords)
if (typedKeywords.exists(_.size > 0)) {
tmp = tmp.add(("keywords" -> typedKeywords.get.map(x => BSONString(x))))
}
val modifier = BSONDocument("$set" -> tmp)
val updateFuture = collection.update(BSONDocument("_id" -> objectId), modifier)
}
UPDATE After looking at the solution from #Vikas it came to me what if there are more (say 10 or 15) number of input Options that I need to deal with. Maybe a fold or reduce based solution will scale better?
In your current code you're adding an empty BSONDocument() if none of those if conditions matched? val modifier = BSONDocument("$set" -> tmp) will have an empty tmp if name was None and typedKeyWords was None. Assuming that's what you want here is one approach to get rid of transient var. also note having a var locally (in a method) isn't a bad thing (sure I'll still make that code look bit prettier)
val typedKeywords : Option[List[String]] = Utils.getKeywords(keywords)
val bsonDoc = (name,typedKeywords) match{
case (Some(n),Some(kw) ) => BSONDocument().add( "name" -> BSONString(n)) .add(("keywords" -> kw.map(x => BSONString(x))))
case (Some(n), None) => BSONDocument().add( "name" -> BSONString(n))
case (None,Some(kw)) => BSONDocument().add(("keywords" -> kw.map(x => BSONString(x))))
case (None,None) => BSONDocument()
}
val modifier = BSONDocument("$set" -> bsonDoc)

How to get a value as number from Mongodb, casbah

I have a simple code for getting the port number from MongoDB. I use scala and the driver is of course casbah.
def getPortNo : Int {
val query = MongoDBObject("_id" -> "Store")
val data = coll.findOne(query)
return data.get("port")
}
Here my application only has one document that id is equal to "store".
but this is not resolved in IDE.
I have the same code for getting the version.
def getVersion : String = {
val query = MongoDBObject("_id" -> "Store")
val data = coll.findOne(query)
return data.get("version").toString
}
this works well.
I tried data.get("port").toString.toInt and It also does not work.
Can someone tell me how to do this. I think the problem here is the returning value in not either number or a string. what is the return type and how can I cast it into a number.
It depends on how you store "port" field. Try data.as[Number]("value").intValue(). It must work any number format.
And you should consider that findOne returns Option, so you can return Option too:
def getPortNo : Option[Int] = {
val query = MongoDBObject("_id" -> "Store")
val data = coll.findOne(query)
data.map(_.as[Number]("port").intValue)
}
Or use some default value:
def getPortNo : Int = {
val query = MongoDBObject("_id" -> "Store")
val data = coll.findOne(query)
data.map(_.as[Number]("port").intValue).getOrElse(80)
}