3

我正在使用带有 scala 的安全社交和响应式mongo 库编写一个 play 2.3 应用程序。现在我正在尝试实现 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 {

怎么了?

4

3 回答 3

2

如果你在 a 上进行映射,Future[A]你最终会得到 a Future[B]T你传递给的 lambda 返回的类型在哪里map

由于该 lambdaFuture[B]在这种情况下返回 a ,因此您最终得到 a Future[Future[B]],它与预期的类型不匹配。

简单的解决方法是使用 a flatMap,它需要一个 lambda 从AFuture[B]


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

把它们加起来

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

顺便说一句,还有很大的改进空间,因为你可以使用一个 for-comprehension 和OptionTscalaz 的 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))
于 2014-09-24T11:19:33.160 回答
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

  }
于 2014-09-24T11:30:43.967 回答
1

如果您真的想快速查找失败(尽管它不是那么有用),然后从数据库中重新加载更新的用户,那么不需要使用 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

  }
于 2014-09-24T13:00:50.393 回答