0

I am confused on how one would conditionally update a document based on a previous query using only futures.

Let say I want to push to some value into an array in a document only if that array has a size less than a given Integer.

I am using this function to get the document, after getting the document I am pushing values - what I am unable to do is do that conditionally.

def joinGroup(actionRequest: GroupRequests.GroupActionRequest): Future[GroupResponse.GroupActionCompleted] = {
//groupisNotFull() is a boolean future
groupIsNotFull(actionRequest.groupId).map(
  shouldUpdate => {
    if(shouldUpdate){
      Logger.info(actionRequest.initiator + " Joining Group: " + actionRequest.groupId)
      val selector = BSONDocument("group.groupid" -> BSONDocument("$eq" -> actionRequest.groupId))
      val modifier = BSONDocument("$push" -> BSONDocument("group.users" -> "test-user"))
      val updateResult = activeGroups.flatMap(_.update(selector, modifier))
        .map(res => {
          GroupActionCompleted(
            actionRequest.groupId,
            actionRequest.initiator,
            GroupConstants.Actions.JOIN,
            res.ok,
            GroupConstants.Messages.JOIN_SUCCESS
          )
        })
        .recover {
          case e: Throwable => GroupActionCompleted(
            actionRequest.groupId,
            actionRequest.initiator, GroupConstants.Actions.JOIN,
            success = false,
            GroupConstants.Messages.JOIN_FAIL
          )
        }
      updateResult
    }
    else {
      val updateResult = Future.successful(
       GroupActionCompleted(
          actionRequest.groupId,
          actionRequest.initiator,
          GroupConstants.Actions.JOIN,
          success = false,
          GroupConstants.Messages.JOIN_FAIL
        ))
      updateResult
    }
  }
)
}

 //returns a Future[Boolean] based on if there is room for another user.
private def groupIsNotFull(groupid: String): Future[Boolean] = {
findGroupByGroupId(groupid)
  .map(group => {
    if (group.isDefined) {
      val fGroup = group.get
      fGroup.group.users.size < fGroup.group.groupInformation.maxUsers
    } else {
      false
    }
  })

}

I am confused on why I cannot do this. The compilation error is:

Error:type mismatch; found : scala.concurrent.Future[response.group.GroupResponse.GroupActionCompleted] required: response.group.GroupResponse.GroupActionCompleted

for both the if and else branch 'updateResult'.

As a side question.. is this the proper way of updating documents conditionally - that is querying for it, doing some logic then executing another query?

cchantep
  • 9,118
  • 3
  • 30
  • 41
stacktraceyo
  • 1,235
  • 4
  • 16
  • 22
  • Why not have a look at `findAndModify` ? – cchantep Jun 22 '17 at 21:46
  • @cchantep as I understand, I can not do conditional updates within a query itself. That is by design after reading the mongodb documentation, that the business logic should be out of the query – stacktraceyo Jun 23 '17 at 13:49
  • It depends what you mean by conditional, `findAndModify` allows to update or delete existing document that match a query. – cchantep Jun 23 '17 at 13:55
  • @cchantep ok, maybe I will see if there is some query I can do that returns the document with a given field with a size – stacktraceyo Jun 23 '17 at 13:59
  • @cchantep I think a better solution will to be adding a new field that acts as a counter than query for size based on that. – stacktraceyo Jun 23 '17 at 14:27

2 Answers2

1

Ok got it - you need to flatMap the first Future[Boolean] like this:

groupIsNotFull(actionRequest.groupId).flatMap( ...

Using flatMap, the result will be a Future[T] with map you would get a Future[Future[T]]. The compiler knows you want to return a Future[T] so its expecting the map to return a T and you are trying to return a Future[T] - so it throws the error. Using flatMap will fix this.

Some further clarity on map vs flatmap here: In Scala Akka futures, what is the difference between map and flatMap?

jsdeveloper
  • 3,945
  • 1
  • 15
  • 14
  • awesome thanks, however I ended up going with a different method to updating it conditionally. I added a field on the document that was decremented everytime the array was added onto. And then the query would select documents that matched the id and that field's value > 0 – stacktraceyo Jun 23 '17 at 17:03
  • No problem. However, since you want to optionally return a document, it may be more 'functional' to return a Future[Option[Document]]. Ie you return Some(doc) if there is a doc or None if its below the size threshold. This might be a nicer pattern than the if/else block. – jsdeveloper Jun 23 '17 at 20:51
0

I believe the problem is because the joinGroup2 function return type is Future[Response], yet you are returning just a Response in the else block. If you look at the signature of the mapTo[T] function, it returns a Future[T].

I think you need to wrap the Response object in a Future. Something like this:

else {
  Future { Response(false, ERROR_REASON) }
}

Btw you have a typo: Respose -> Response

jsdeveloper
  • 3,945
  • 1
  • 15
  • 14
  • `Future.successful` for pure value – cchantep Jun 22 '17 at 21:46
  • Wrapping it in the future still gives me an error: Error type mismatch; found : scala.concurrent.Future[response.Response] required: response.Response I think it is because the if statement has a second map function inside with a nested future, I am a little shaky on how to use flatMap for this case – stacktraceyo Jun 23 '17 at 12:44
  • did you try Future.successful(Response(false, "FAILURE")) ? – jsdeveloper Jun 23 '17 at 12:49
  • I am updating the question with a less contrived example and the actual code I am working with – stacktraceyo Jun 23 '17 at 13:36