mongodbscalareactivemongoplay-reactivemongo

ReactiveMongo conditional update


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?


Solution

  • 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?