3

i'm writing a play 2.3 application using secure social and reactivemongo library, with scala. Now i'm trying to implement the UserService[T] trait but i'm getting compile errors on the updatePasswordInfo method. This is the method:

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
    }

  }

And this is the compiler error:

/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 {

What's wrong?

alberto adami
  • 729
  • 1
  • 6
  • 25

3 Answers3

2

If you map over a Future[A] you'll end up with a Future[B], where T is the type returned from the lambda you pass to map.

Since that lambda is returning a Future[B] in this case you end up with a Future[Future[B]], which doesn't match the expected type.

The easy fix is to use a flatMap, which takes a lambda going from A to Future[B].


Also, you're returning an Option[LoginUser] but the method is expected to return an Option[BasicProfile]. The compiler inferred a common supertype, which in this case is Product with Serializable, since they're both case classes.

To sum it up

scala.concurrent.Future[Product with Serializable]
^_____________________^^_________________________^
          1                        2
  1. use flatMap instead of map
  2. return a BasicProfile instead of LoginUser, or change the method return type to Future[Option[LoginUser]]

By the way, there's a lot of room for improvement, as you could use a for-comprehension and the OptionT monad transformer from scalaz to make the whole thing prettier.

Here's an example

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

By the way, this works under the assumption that update is a sync call, which might (and I hope) not be the case. If it returns a Future[T] just change the code to

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

or if it already returns a Future[Option[T]]

_ <- optionT(UserServiceLogin.update(query, newPassword))
Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
  • LoginUser is a subtype of BasicProfile class. The compiler give me an error: he said that don't find optionT – alberto adami Sep 24 '14 at 12:13
  • 1
    @albertoadami, well that's becasue you need to add ScalaZ to your dependencies. Anyway in this case you can easily do without, since you don't need to do anything with the `user` object. – Gabriele Petronella Sep 24 '14 at 13:17
1

There are several ways in which your code can be improved.

For example, you don't need to find the user before firing the query.

Also, it would be good to check if your query actually succeeded (if the API allows it).

Third, I am not sure in which way LoginUser corresponds to the BasicProfile. Your code doesn't seem to do any kind of conversion. If LoginUser is a subclass of BasicProfile, or can be cast to BasicProfile somehow, you can try something like this:

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

  }
Ashalynd
  • 12,363
  • 2
  • 34
  • 37
1

If you really want to do the find to fail fast (though it is not so useful) and then reload the updated user from the database, something like this should do without the need for using 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

  }
Jean
  • 21,329
  • 5
  • 46
  • 64