Create object in database through socket - scala

In my socket I receive object which I should add to my database if it doesn't exists, here is my code but the problem is that nothing is added to database (the method create works fine):
def receive = {
case result: Result =>
sessionService.findById(result.id).flatMap {
case Some(session) =>
Future.successful(out ! Json.toJson("exists"))
case None =>
val session = Session(
Option(java.util.UUID.randomUUID),
java.util.UUID.randomUUID,
java.util.UUID.randomUUID,
resultItem.versionId,
java.util.UUID.randomUUID,
"text",
"text",
null,
null,
Option(result.scores),
null)
sessionService.create(session).map {
case Right(_id) =>
println(Json.toJson(Json.obj("_id" -> _id)))
case Left(error) =>
println(Json.toJson("message" -> error))
}
}
}
UPDATE:
here is the create method:
def create(session: Session): Future[Either[String, UUID]] = {
println("here")
collection.insert(session).map {
case result if result.ok == true => Right(session._id.get)
case result => Left(result.writeErrors(0).errmsg)
}
}

Related

Scala Playframework not all DB queries get executed

I'm sending via HTTP Post Request a Json to my Playframework backend.
In my backend I validate the Json to a Model. After that, I want to save the entries in my Model to my DB.
def parseJSON: Action[AnyContent] = Action.async {
request =>
Future {
request.body.asJson.map(_.validate[MyModel] match {
case JsSuccess(items, _) =>
itemsToDBController.saveItems(items)
Ok("Success")
case JsError(err) =>
println(err)
BadRequest("Json Parse Error")
}).getOrElse(BadRequest("Error"))
}
}
One Item consists out of several objects. To save all objects to my DB, I need to get some values. Therefore I'm using a for(..) yield(...):
def saveItems(items: MyModel) = {
items.SomeObject.map(obj => {
if (obj.value1.isDefined &&
obj.value2.isDefined ) {
val result = for (
value1Exists <- value1DTO.checkExists(obj.value1.name);
value1Entry <- getOrCreateValue1(value1Exists, obj);
value2Exists <- value2DTO.checkExists(obj.value2.name);
value2Entry <- getOrCreateValue2(value1Exists, obj)
) yield(value1Entry, value2Entry)
result.map({
case (value1Entry, value2Entry) => {
insertAllValue3(value1Entry, value2Entry)
Future.successful()
}
case _ => Future.failed(new Exception("Not all entries defined"))
})
}
else {
Future.successful("Not all objects defined - skipping")
}
})
}
My problem is, after all result.map({...}) have started, my parseJSON Action returns 200 - OK. But not all relevant items get stored to my DB. It seems like after the 200 - OK everything is stopped and it doesn't even throw an error.
I don't want to use Await.result or anything blocking in my Action.
Thanks in Advance
You are starting computations by calling itemsToDBController.saveItems(items) and then immediately return result with Ok("Success"). So exception may be thrown after request if completed.
To fix this issue you need to transform result of itemsToDBController.saveItems from List[Future[T]] to Future[List[T]] with help of Future.sequence. Then call map method on returned future. Call recover on this Future to find which error is thrown:
def parseJSON: Action[AnyContent] = Action.async { request =>
request.body.asJson
.map(_.validate[MyModel] match {
case JsSuccess(items, _) =>
Future
.sequence(itemsToDBController.saveItems(items))
.map(_ => Ok("Success"))
.recover {
case e: Exception => BadRequest(e.getMessage())
}
case JsError(err) =>
println(err)
Future.successful(BadRequest("Json Parse Error"))
})
.getOrElse(Future.successful(BadRequest("Error")))
}
Update
For running all inserts in one transaction you should combine DBIOAction instead of Future. For example you rewrite checkExists(name) as:
def checkExists(name: String): DBIO[Boolean] = {
Objects.filter(obj => obj.name === name).exists
}
getOrCreateValue(exists, obj) as:
def getOrCreateValue(exists: boolean, obj: Object): DBIO[Object] = {
if (exists) {
Objects.filter(o => o.name === name).result.head
} else {
(Objects returning Objects.map(_.id) into ((o, id) => o.copy(id = Some(id)))) += obj
}
}
Now you can run it in single transaction in the following way:
def saveItems(items: MyModel) = {
val insertActions = items.SomeObject.map(obj => {
if (obj.value1.isDefined && obj.value2.isDefined) {
val result = for {
value1Exists <- value1DTO.checkExists(obj.value1.name);
value1Entry <- getOrCreateValue1(value1Exists, obj);
value2Exists <- value2DTO.checkExists(obj.value2.name);
value2Entry <- getOrCreateValue2(value1Exists, obj)
} yield (value1Entry, value2Entry)
result.flatMap({
case (value1Entry, value2Entry) => {
insertAllValue3(value1Entry, value2Entry) // This also returns instance of `DBIOAction`
}
case _ =>
DBIO.failed(new Exception("Not all entries defined"))
})
} else {
DBIO.successful("Not all objects defined - skipping")
}
})
db.run(DBIO.sequence(inserActions).transactionally)
}
For mo info how to work with DBIO actions check this official docs

Play framework ignore following map/flatmap

Is there a way to ignore the following map/flatmap's without failed?
This is what I have:
def delete(serverId: UUID) = authAction.async { implicit request =>
val user = request.user.get
serverService.findByIdAndUserId(serverId, user.id.get)
.flatMap{s =>
if (s.isEmpty) {
Future.failed(new DeleteFailedException)
// Can I return a `NotFound("...")` here instead of failed?
} else {
Future.successful(s.get)
}
}
.map{s =>
serverService.delete(s)
}.map{_ =>
Ok(Json.toJson(Map("success" -> "true")))
}
}
When I would return a NotFound("...") in the flatMap the following map would still be executed. Is there a way to ignore the following map/flatmap's?
Think so should be fine (I assumed that findByIdAndUserId returns Future[Option[_]], not an Option[_] as you answered in comment). In my approach I also removed usage of get and unnecessary map
def delete(serverId: UUID) = authAction.async { implicit request =>
val user = request.user.get
request.user.flatMap(_.id).fold {
Future.successfull(NotFound("no user"))
} {userId =>
serverService.findByIdAndUserId(serverId, userId).map {
case None =>
NotFound("no server")
case Some(s) =>
serverService.delete(s)
Ok(Json.toJson(Map("success" -> "true")))
}
}
}
Instead of doing a Future.failed with an exception. you can return an Either. The good thing about either is that you can pattern match on it and then construct the appropriate http response.
def delete(serverId: UUID) = authAction.async { implicit request =>
val user = request.user.get
serverService.findByIdAndUserId(serverId, user.id.get)
.flatMap{s =>
if (s.isEmpty) {
Left(new DeleteFailedException)
} else {
Right(s.get)
}
}
.map{s => s match {
case Left(notFound) =>
// construct the Json for failure
case Right(s) =>
serverService.delete(s)
// construct json for success.
}}
}

ReactiveMongo query returning None

I am just new to learning Scala and the related technologies.I am coming across the problem where the loadUser should return a record but its coming empty.
I am getting the following error:
java.util.NoSuchElementException: None.get
I appreciate this is not ideal Scala, so feel free to suggest me improvements.
class MongoDataAccess extends Actor {
val message = "Hello message"
override def receive: Receive = {
case data: Payload => {
val user: Future[Option[User]] = MongoDataAccess.loadUser(data.deviceId)
val twillioApiAccess = context.actorOf(Props[TwillioApiAccess], "TwillioApiAccess")
user onComplete {
case Failure(exception) => println(exception)
case p: Try[Option[User]] => p match {
case Failure(exception) => println(exception)
case u: Try[Option[User]] => twillioApiAccess ! Action(data, u.get.get.phoneNumber, message)
}
}
}
case _ => println("received unknown message")
}
}
object MongoDataAccess extends MongoDataApi {
def connect(): Future[DefaultDB] = {
// gets an instance of the driver
val driver = new MongoDriver
val connection = driver.connection(List("192.168.99.100:32768"))
// Gets a reference to the database "sensor"
connection.database("sensor")
}
def props = Props(new MongoDataAccess)
def loadUser(deviceId: UUID): Future[Option[User]] = {
println(s"Loading user from the database with device id: $deviceId")
val query = BSONDocument("deviceId" -> deviceId.toString)
// By default, you get a Future[BSONCollection].
val collection: Future[BSONCollection] = connect().map(_.collection("profile"))
collection flatMap { x => x.find(query).one[User] }
}
}
Thanks
There is no guaranty the find-one (.one[T]) matches at least one document in your DB, so you get an Option[T].
Then it's up to you to consider (or not) that having found no document is a failure (or not); e.g.
val u: Future[User] = x.find(query).one[User].flatMap[User] {
case Some(matchingUser) => Future.successful(matchingUser)
case _ => Future.failed(new MySemanticException("No matching user found"))
}
Using .get on Option is a bad idea anyway.

PlayFramework: Check existing of record before creation

I want to create a registration Action in play framework application. The question is how to implement check for already existing email?
object AccountController extends Controller {
case class AccountInfo(email: String, password: String)
val accountInfoForm = Form(
mapping(
"email" -> email,
"password" -> nonEmptyText
)(AccountInfo.apply)(AccountInfo.unapply)
)
def createAccount = Action {
implicit request => {
accountInfoForm.bindFromRequest fold (
formWithErrors => {
Logger.info("Validation errors")
BadRequest(accountInfoForm.errorsAsJson)
},
accountInfo => {
AccountService.findByEmail(accountInfo.email) map {
case accountOpt: Option[Account] => accountOpt match {
case Some(acc) => {
Logger.info("Email is already in use")
BadRequest(Json.toJson(Json.obj("message" -> "Email is already in use")))
}
case None => {
Logger.info("Created account")
val account = Account.createAccount(accountInfo)
val accountToSave = account copy (password=Account.encryptPassword(accountInfo.password))
Created(Json.toJson(AccountService.add(accountToSave)))
}
}
case _ => {
Logger.info("DB connection error")
InternalServerError(Json.toJson(Json.obj("message" -> "DB connection error")))
}
}
Ok("Ok")
})
}
}
}
AccountService.findByEmail - returns Future[Option[Account]]
Unfortunately my solution always return 'Ok'
Since findByEmail returns a Future, you should use Action.async instead. This is because when you map the Future[Option[Account]], you're mapping it to a Future[Result] instead of Result. Note how I had to use Future.successful with formWithErrors to keep the return type the same.
When returning a simple Result use Action. When returning a Future[Result], use Action.async.
def createAccount = Action.async {
implicit request => {
accountInfoForm.bindFromRequest fold (
formWithErrors => {
Logger.info("Validation errors")
Future.successful(BadRequest(accountInfoForm.errorsAsJson))
},
accountInfo => {
AccountService.findByEmail(accountInfo.email) map {
case accountOpt: Option[Account] => accountOpt match {
case Some(acc) => {
Logger.info("Email is already in use")
BadRequest(Json.toJson(Json.obj("message" -> "Email is already in use")))
}
case None => {
Logger.info("Created account")
val account = Account.createAccount(accountInfo)
val accountToSave = account copy (password=Account.encryptPassword(accountInfo.password))
Created(Json.toJson(AccountService.add(accountToSave)))
}
}
case _ => {
Logger.info("DB connection error")
InternalServerError(Json.toJson(Json.obj("message" -> "DB connection error")))
}
}
})
}
}

Optional Json Body Parser

I'm trying to write a DRY CRUD restful service using PlayFramework. Here's the code for it.
def crudUsers(operation: String) = Action(parse.json) { request =>
(request.body).asOpt[User].map { jsonUser =>
try {
DBGlobal.db.withTransaction {
val queryResult = operation match {
case "create" =>
UsersTable.forInsert.insertAll(jsonUser)
Json.generate(Map("status" -> "Success", "message" -> "Account inserted"))
case "update" =>
val updateQ = UsersTable.where(_.email === jsonUser.email.bind).map(_.forInsert)
println(updateQ.selectStatement)
updateQ.update(jsonUser)
Json.generate(Map("status" -> "Success", "message" -> "Account updated"))
case "retrieve" =>
val retrieveQ = for(r <- UsersTable) yield r
println(retrieveQ.selectStatement)
Json.generate(retrieveQ.list)
case "delete" =>
val deleteQ = UsersTable.where(_.email === jsonUser.email)
deleteQ.delete
Json.generate(Map("status" -> "Success", "message" -> "Account deleted"))
}
Ok(queryResult)
}
} catch {
case _ =>
val errMsg: String = operation + " error"
BadRequest(Json.generate(Map("status" -> "Error", "message" -> errMsg)))
}
}.getOrElse(BadRequest(Json.generate(Map("status" -> "Error", "message" -> "error"))))
}
}
I've noticed that update, delete and create operations work nicely. However the retrieve operation fails with For request 'GET /1/users' [Invalid Json]. I'm pretty sure this is because the JSON parser is not tolerant of a GET request with no JSON passed in the body.
Is there a way to special case the GET/Retrieve operation without losing losing the DRY approach I've started here?
My guess would be that you split your methods so that you can create a different routes for the ones with and without body.
It seems that the design of the code would not work even if the empty string was parsed as JSON. The map method would not be executed because there would be no user. That would cause the match on operation to never be executed.
Update
Since you mentioned DRY, I would refactor it into something like this:
type Operations = PartialFunction[String, String]
val operations: Operations = {
case "retrieve" =>
println("performing retrieve")
"retrieved"
case "list" =>
println("performing list")
"listed"
}
def userOperations(user: User): Operations = {
case "create" =>
println("actual create operation")
"created"
case "delete" =>
println("actual delete operation")
"updated"
case "update" =>
println("actual update operation")
"updated"
}
def withoutUser(operation: String) = Action {
execute(operation, operations andThen successResponse)
}
def withUser(operation: String) = Action(parse.json) { request =>
request.body.asOpt[User].map { user =>
execute(operation, userOperations(user) andThen successResponse)
}
.getOrElse {
errorResponse("invalid user data")
}
}
def execute(operation: String, actualOperation: PartialFunction[String, Result]) =
if (actualOperation isDefinedAt operation) {
try {
DBGlobal.db.withTransaction {
actualOperation(operation)
}
} catch {
case _ => errorResponse(operation + " error")
}
} else {
errorResponse(operation + " not found")
}
val successResponse = createResponse(Ok, "Success", _: String)
val errorResponse = createResponse(BadRequest, "Error", _: String)
def createResponse(httpStatus: Status, status: String, message: String): Result =
httpStatus(Json.toJson(Map("status" -> status, "message" -> message)))