在Play中的Future [Option [BasicProfile]]方法上编译错误

时间:2014-09-24 10:36:37

标签: scala playframework-2.0 securesocial reactivemongo play-reactivemongo

我正在编写一个使用安全社交和反应式图书馆的游戏2.3应用程序,使用scala。 现在我试图实现UserService [T]特性,但我在updatePasswordInfo方法上遇到编译错误。 这是方法:

def updatePasswordInfo(user: LoginUser,info: PasswordInfo): scala.concurrent.Future[Option[BasicProfile]] = {
    implicit val passwordInfoFormat = Json.format[PasswordInfo]
    //the document query
    val query = Json.obj("providerId" -> user.providerId,
                         "userId" -> user.userId
                        )
    //search if the user exists
    val futureUser: Future[Option[LoginUser]] = UserServiceLogin.find(query).one
    futureUser map {
      case Some(x) => val newPassword = Json.obj("passswordInfo" -> info)// the new password
                      UserServiceLogin.update(query, newPassword) //update the document
                      val newDocument: Future[Option[LoginUser]] = UserServiceLogin.find(query).one
                      newDocument map {
                        case Some(x) => x
                        case None => None

                      } //return the new LoginUser
      case None => None
    }

  }

这是编译器错误:

/Users/alberto/git/recommendation-system/app/security/UserService.scala:203: type mismatch;
[error]  found   : scala.concurrent.Future[Product with Serializable]
[error]  required: Option[securesocial.core.BasicProfile]
[error]                       newDocument map {

出了什么问题?

3 个答案:

答案 0 :(得分:2)

如果您映射Future[A],那么您最终会得到Future[B],其中T是您传递给map的lambda返回的类型。

由于lambda在这种情况下返回Future[B],因此最终得到的Future[Future[B]]与期望的类型不匹配。

简单的解决方法是使用flatMap,它将lambda从A转移到Future[B]


此外,您还要返回Option[LoginUser],但该方法应返回Option[BasicProfile]。编译器推断出一个常见的超类型,在这种情况下是Product with Serializable,因为它们都是案例类。

总结一下

scala.concurrent.Future[Product with Serializable]
^_____________________^^_________________________^
          1                        2
  1. 使用flatMap代替map
  2. 返回BasicProfile而不是LoginUser,或将方法返回类型更改为Future[Option[LoginUser]]

  3. 顺便说一下,还有很大的改进空间,因为你可以使用for-comprehension和来自scalaz的OptionT monad变换器来使整个事情变得更漂亮。

    这是一个例子

    import scalaz._; import Scalaz._
    
    val newPassword = Json.obj("passswordInfo" -> info)
    (for {
      // this is done only for failing fast in case the user doesn't exist
      _ <- optionT(UserServiceLogin.find(query).one)
      _ <- optionT(Future.successful(Some(UserServiceLogin.update(query, newPassword))))
      updatedUser <- optionT(UserServiceLogin.find(query).one)
    } yield updatedUser).run
    

    顺便说一句,这是假设update是同步调用,这可能(并且我希望)不是这种情况。如果它返回Future[T],只需将代码更改为

    _ <- optionT(UserServiceLogin.update(query, newPassword).map(Some(_)))
    

    或者它已经返回Future[Option[T]]

    _ <- optionT(UserServiceLogin.update(query, newPassword))
    

答案 1 :(得分:1)

有几种方法可以改进您的代码。

例如,您不需要在触发查询之前找到用户。

此外,最好检查您的查询是否实际成功(如果API允许)。

第三,我不确定LoginUser与BasicProfile的对应方式。您的代码似乎没有进行任何类型的转换。 如果 LoginUser是BasicProfile的子类,或者可以以某种方式强制转换为BasicProfile,您可以尝试这样的事情:

def updatePasswordInfo(user: LoginUser,info: PasswordInfo): scala.concurrent.Future[Option[BasicProfile]] = {
    implicit val passwordInfoFormat = Json.format[PasswordInfo]
    //the document query
    val query = Json.obj("providerId" -> user.providerId,
                         "userId" -> user.userId
                        )
    UserServiceLogin.update(query, newPassword) //update the document
    for {
        user <- UserServiceLogin.find(query).one
    } yield user.map(_.asInstanceOf[BasicProfile]) //return the new LoginUser

  }

答案 2 :(得分:1)

如果你真的想让find快速失败(虽然它没那么有用),然后从数据库重新加载更新的用户,这样的事情应该不需要使用scalaz:

def updatePasswordInfo(user: LoginUser,info: PasswordInfo): scala.concurrent.Future[Option[BasicProfile]] = {
    implicit val passwordInfoFormat = Json.format[PasswordInfo]
    //the document query
    val query = Json.obj("providerId" -> user.providerId,
                         "userId" -> user.userId)
    val newPassword = Json.obj("passswordInfo" -> info)
    //update the document
    for {
        userO <- UserServiceLogin.find(query).one[BasicProfile] //fail fast (not sure this is really useful)
        updatedUser<-UserServiceLogin.update(query, newPassword).map(_=>userO).recover{case _ =>None}
        actualUser <- UserServiceLogin.find(query).one[BasicProfile]
    } yield actualUser

  }