How to get a value as number from Mongodb, casbah - mongodb

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

Related

Pass map to slick filter and filter by the values in the map

I'm new to scala, and, I'm trying to pass a map i.e. Map[String, Any]("from_type" -> "Admin", "from_id" -> 1) to my service for dynamic filtering. I'm trying to avoid writing my code like this filter(_.fromType === val && _.fromId === val2)
When trying this example Slick dynamically filter by a list of columns and values
I get a Type mismatch. Required Function1[K, NotInfered T] Found: Rep[Boolean]
Service code:
val query = TableQuery[UserTable]
def all(perPage: Int page: Int, listFilters: Map[String, Any]): Future[ResultPagination[User]] = {
val baseQuery = for {
items <- query.filter( listFilters ).take(perPage).drop(page).result // <----I want to filter here
total <- query.length.result
} yield ResultPagination[User](items, total)
db.run(baseQuery)
}
Table code:
def fromId: Rep[Int] = column[Int]("from_id")
def fromType: Rep[String] = column[String]("from_type")
def columnToRep(column: String): Rep[_] = {
column match {
case "from_type" = this.fromType
case "from_id" = this.fromId
}
}
Well, I would not recommend to use Map[String, Any] construction, because of using Any you are loosing type safety: for instance you can pass to the function by mistake Map("fromId" -> "1") and compile won't help identify issue.
I guess, what you want is to pass some kind of structure representing variative filter. And Query.filterOpt can help you in this case. You can take a look usage examples at: https://scala-slick.org/doc/3.3.2/queries.html#sorting-and-filtering
Please, see code example below:
// Your domain filter structure. None values will be ignored
// So `UserFilter()` - will match all.
case class UserFilter(fromId: Option[Int] = None, fromString: Option[String] = None)
def all(perPage: Int, page: Int, filter: UserFilter): Future[ResultPagination[User]] = {
val baseQuery = for {
items <- {
query
.filterOpt(filter.fromId)(_.fromId === _)
.filterOpt(filter.fromString)(_.fromType === _)
.take(perPage)
.drop(page)
.result
}
total <- query.length.result
} yield ResultPagination[User](items, total)
db.run(baseQuery)
}
And this will type safe.
Hope this helps!

Updating nested array inside array using reactivemongo

I'm trying to update an element in nested lists with scala and reactive mongo. I tried this:
def updatethirdList(secondListElementId: UUID, firstObject: FirstObject): Future[Either[String, UUID]] = {
val query = Json.obj("secondList.thirdList._id" -> secondListElementId)
val update = Json.obj("$set" -> Json.obj("secondList.$.thirdList" -> sessionType))
collection.update(query, update).map {
case result if result.ok == true => Right(secondListElementId)
case result => Left(result.message)
}
}
the structure:
{
"firstList":[{
"secondList":[{
"thirdList":[{
"firstObject":""
}]
}]
}]
}
the problem with my code is that it's gave this result:
firstList.0.secondList.0.thirdList = firstObject
it should give for example this :
firstList.0.secondList.0.thirdList.0 = firstObject
I tried this:
val update = Json.obj("$set" -> Json.obj("secondList.$.thirdList.$" -> firstObject))
but I get this error: MongoDB: Too many positional (i.e. '$') elements found in path
Any help please

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)

Play reactivemongo doesnt stop

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.

How to query with '$in' over '_id' in reactive mongo and play

I have a project set up with playframework 2.2.0 and play2-reactivemongo 0.10.0-SNAPSHOT. I'd like to query for few documents by their ids, in a fashion similar to this:
def usersCollection = db.collection[JSONCollection]("users")
val ids: List[String] = /* fetched from somewhere else */
val query = ??
val users = usersCollection.find(query).cursor[User].collect[List]()
As a query I tried:
Json.obj("_id" -> Json.obj("$in" -> ids)) // 1
Json.obj("_id.$oid" -> Json.obj("$in" -> ids)) // 2
Json.obj("_id" -> Json.obj("$oid" -> Json.obj("$in" -> ids))) // 3
for which first and second return empty lists and the third fails with error assertion 10068 invalid operator: $oid.
NOTE: copy of my response on the ReactiveMongo mailing list.
First, sorry for the delay of my answer, I may have missed your question.
Play-ReactiveMongo cannot guess on its own that the values of a Json array are ObjectIds. That's why you have to make a Json object for each id that looks like this: {"$oid": "526fda0f9205b10c00c82e34"}. When the ReactiveMongo Play plugin sees an object which first field is $oid, it treats it as an ObjectId so that the driver can send the right type for this value (BSONObjectID in this case.)
This is a more general problem actually: the JSON format does not match exactly the BSON one. That's the case for numeric types (BSONInteger, BSONLong, BSONDouble), BSONRegex, BSONDateTime, and BSONObjectID. You may find more detailed information in the MongoDB documentation: http://docs.mongodb.org/manual/reference/mongodb-extended-json/ .
I managed to solve it with:
val objectIds = ids.map(id => Json.obj("$oid" -> id))
val query = Json.obj("_id" -> Json.obj("$in" -> objectIds))
usersCollection.find(query).cursor[User].collect[List]()
since play-reactivemongo format considers BSONObjectID only when "$oid" is followed by string
implicit object BSONObjectIDFormat extends PartialFormat[BSONObjectID] {
def partialReads: PartialFunction[JsValue, JsResult[BSONObjectID]] = {
case JsObject(("$oid", JsString(v)) +: Nil) => JsSuccess(BSONObjectID(v))
}
val partialWrites: PartialFunction[BSONValue, JsValue] = {
case oid: BSONObjectID => Json.obj("$oid" -> oid.stringify)
}
}
Still, I hope there is a cleaner solution. If not, I guess it makes it a nice pull request.
I'm wondering if transforming id to BSONObjectID isn't more secure this way :
val ids: List[String] = ???
val bsonObjectIds = ids.map(BSONObjectID.parse(_)).collect{case Success(t) => t}
this will only generate valid BSONObjectIDs (and discard invalid ones)
If you do it this way :
val objectIds = ids.map(id => Json.obj("$oid" -> id))
your objectIds may not be valid ones depending on string id really being the stringify version of a BSONObjectID or not
If you import play.modules.reactivemongo.json._ it work without any $oid formatters.
import play.modules.reactivemongo.json._
...
val ids: Seq[BSONObjectID] = ???
val selector = Json.obj("_id" -> Json.obj("$in" -> ids))
usersCollection.find(selector).cursor[User].collect[Seq]()
I tried with the following and it worked for me:
val listOfItems = BSONArray(51, 61)
val query = BSONDocument("_id" -> BSONDocument("$in" -> listOfItems))
val ruleListFuture = bsonFutureColl.flatMap(_.find(query, Option.empty[BSONDocument]).cursor[ResponseAccDataBean]().
collect[List](-1, Cursor.FailOnError[List[ResponseAccDataBean]]()))