0
votes

I'm trying to build a REST API using play 2.0. I have a User case class that contains some fields (like username & password) that shouldn't be updatable by the updateMember method.

Is there a good, functional way, of dealing with multiple Options somehow, because request.body.asJson returns an Option[JsValue], and my user lookup also returns an Option:

package controllers.api

import org.joda.time.LocalDate
import play.api.Play.current
import play.api.db.slick.{DB, Session}
import play.api.mvc._
import play.api.libs.json._
import play.api.libs.functional.syntax._
import models.{Gender, User, UserId}
import repositories.UserRepository

object Member extends Controller {
  def updateMember(id: Long) = Action {
DB.withSession {
  implicit session: Session =>

    val json: JsValue = request.body.asJson              // how to deal with this?

    val repository = new UserRepository
    repository.findById(new UserId(id)).map {
      user =>
        def usernameAppender = __.json.update(
          __.read[JsObject].map { o => o ++ Json.obj("username" -> user.username) }
        )

        json.transform(usernameAppender)              // transform not found

        Ok("updated")
    }.getOrElse(NotFound)
  }
 }
}

I could move the map call to where I try to parse the request, but then inside there I guess I'd need another map over the user Option like I already have. So in that style, I'd need a map per Option.

Is there a better way of dealing with multiple Options like this in FP?

1

1 Answers

0
votes

You're basically dealing with a nested monad, and the main tool for working with such is flatMap, particularly if both options being None has the same semantic meaning to your program:

request.body.asJson.flatMap { requestJson =>
  val repository = new UserRepository
  repository.findById(new UserId(id)).map { user =>
    def usernameAppender = __.json.update(
      __.read[JsObject].map { o => o ++ Json.obj("username" -> user.username) }
    )

    requestJson.transform(usernameAppender)

    Ok("updated") // EDIT: Do you not want to return the JSON?
  }
}.getOrElse(NotFound)

But it might also be the case that your Nones have different meanings, in which case, you probably just want to pattern match, and handle the error cases separately:

request.body.asJson match { 
  case Some(requestJson) =>
    val repository = new UserRepository
    repository.findById(new UserId(id)).map { user =>
      def usernameAppender = __.json.update(
        __.read[JsObject].map { o => o ++ Json.obj("username" -> user.username) }
      )

      requestJson.transform(usernameAppender)

      Ok("updated")
    }.getOrElse(NotFound)
  case None => BadRequest // Or whatever you response makes sense for this case
}