0

Environment: Play! 2.2.3, ReactiveMongo 0.10.0-SNAPSHOT

Suppose I have a page with a list of documents (let's say "projects") and a button that pops up a modal dialogue with fields to be filled in. Upon pressing the OK button the page sends a request with JSON body to the backend:

{
    name: "Awesome Project", 
    url: "https://github.com/ab/cd", 
    repository: "git@github.com/ab/cd.git", 
    script: "empty"
}

The backend routes the request to the Action defined like this:

  def projectsCollection: JSONCollection = db.collection[JSONCollection]("projects")

  def create = Action.async(parse.json) { request =>
    projectsCollection.insert(request.body) map {
      case LastError(true,_,_,_,Some(doc),_,_) =>  Created(JsObject(List(
        "result" -> JsString("OK") ,
        "doc" -> BSONFormats.toJSON(doc)
      )))
      case LastError(false, err, code, msg, _, _, _) => NotAcceptable(JsObject(List(
        "result" -> JsString("ERROR"),
        "error" -> JsString(err.getOrElse("unknown")),
        "code" -> JsNumber(code.getOrElse[Int](0)),
        "msg" -> JsString(msg.getOrElse("no messsage"))
      )))
    }
  }

The LastError case class has a parameter originalDocument: Option[BSONDocument] which is returned in the request response body, but it isn't the document I expected. I want the document with the BSONObjectID filled or at least the _id itself.

Trying to retrieve the freshly created document led me into a dead end, because everything is wrapped into Future.

How to write elegant code that does the task?

Rajish
  • 6,755
  • 4
  • 34
  • 51
  • Are you need to access of _id in case of error? – sh1ng Nov 13 '13 at 09:35
  • @sh1ng I didn't think about it as this case is for creating a new entry so in case of error the `_id` doesn't exist anyway, but yes, having the `_id` of failed update or delete can be useful. – Rajish Nov 13 '13 at 13:17

3 Answers3

2

One possible solution is to generate the _id field yourself and concatenate it with the request body:

val json = Json.obj("_id" -> BSONFormats.toJSON(BSONObjectID.generate)) ++ request.body.as[JsObject]

Then use the json value as the insert parameter, and put into the successful result body.

projectsCollection.insert(json) map {
  case LastError(true,_,_,_,Some(doc),_,_) =>  Created(JsObject(List(
    "result" -> JsString("OK") ,
    "doc" -> BSONFormats.toJSON(doc),
    "project" -> json
  )))
Rajish
  • 6,755
  • 4
  • 34
  • 51
0

I'm new to Play, scala, and reactivemongo, so please excuse my answer if it's ugly. However, I did get an insert, then get working:

def createUser = Action.async(parse.json) { request =>
/*
 * request.body is a JsValue.
 * There is an implicit Writes that turns this JsValue as a JsObject,
 * so you can call insert() with this JsValue.
 * (insert() takes a JsObject as parameter, or anything that can be
 * turned into a JsObject using a Writes.)
 */
val userResult = request.body.validate[User]
userResult.fold(
errors => {
    Future.successful(
    Result(
        header = ResponseHeader(400, Map(CONTENT_TYPE->"application/json")),
        body = Enumerator(Json.obj("status" ->"KO", "message" -> JsError.toFlatJson(errors)).toString.getBytes())
        ))
},
user => blocking{ 
  // `user` is an instance of the case class `models.User`
  val insertDate = new DateTime()
  val newUser = user.copy(created_at = Some(insertDate) )

  collection.insert(newUser).map { lastError => 
    Logger.debug(s"Successfully inserted with LastError: $lastError")
    val cursor = collection.find(newUser).cursor[User]

     // gather all the JsObjects in a list
val futureUsersList: Future[List[User]] = cursor.collect[List]()


// transform the list into a JsArray
val futureUsersJsonArray: Future[JsArray] = futureUsersList.map { persons =>
  Json.arr(persons)
}

// everything's ok! Let's reply with the array
val res = futureUsersJsonArray.map { persons =>

    Created(persons)
  } 
   Await.result(res, Duration(1000, MILLISECONDS))
}


}

) }

Jon
  • 7,848
  • 1
  • 40
  • 41
0

I'm new to this as well, but in reactive-mongo 0.11 they've removed the LastError structure in favor of a WriteResult. The WriteResult does have an originalDocument field on it, but it's unfortunately set to None with a comment 'TODO'.

What I came up with to return the created document (minus the _id field),

  def promiseOfWriteResult[T](writeResult:Future[WriteResult])(value:T) = {
    val p = Promise[T]
    writeResult map {
      case ok if ok.ok   => p.success(value)
      case error         => p.failure(error.getCause)
    } recover { // !important
      case x : Throwable => p.failure(x)
    }
    p.future
  }

This would be used like,

promiseOfWriteResult(collection.insert(doc))(doc)
nlucaroni
  • 47,556
  • 6
  • 64
  • 86
  • 1
    What is the purpose of this given the fact that you still do not have the newly inserted object id? – joesan Oct 24 '15 at 06:42