2014-02-11 12 views
6

Einfacher Code, der Benutzer nach Pass überprüfen sollte, Benutzer ist aktiv und nach dem letzten Login Datetime aktualisieren.Scala Async/Callback Code Neuschreiben

def authenticate() = Action.async { implicit request => 
    loginForm.bindFromRequest.fold(
     errors => Future.successful(BadRequest(views.html.logon(errors))), 
     usersData =>{ 
      val cursor = this.collection.find(BSONDocument("name" -> usersData._1)).one[Account].map(_.filter(p=>p.password == hashedPass(usersData._2, usersData._1))) 
      cursor.flatMap(p => p match { 
       case None => Future.successful(BadRequest(views.html.logon(loginForm.withGlobalError("user/pass incorect!!!")))) 
       case Some(user) => { 
       if(!user.active) 
        Future.successful(BadRequest(views.html.logon(loginForm.withGlobalError("inactive!!!")))) 
       else collection.update(BSONDocument("_id" -> user.id), 
          BSONDocument("$set" -> 
          BSONDocument("lastLogin" -> BSONDateTime(new org.joda.time.DateTime().getMillis())))) 
          .flatMap(x => gotoLoginSucceeded(user.id.stringify)) 

       } 
       }) 
      }) 
    } 

Wie umschreiben Sie es in weniger flachMap/Karte Spaghetti?

Eine andere Lösung

def authenticate() = AsyncStack { implicit request => 
loginForm.bindFromRequest.fold(
    errors => Future.successful(BadRequest(views.html.logon(errors))), 
    usersData =>{ 
     for{ 
     user <- this.collection.find(BSONDocument("name" -> usersData._1)).one[Account].map(_.filter(p=>p.password == hashedPass(usersData._2, usersData._1))) 
     update <- { 
     lazy val update = collection.update(BSONDocument("_id" -> user.get.id), 
     BSONDocument("$set" -> 
     BSONDocument("lastLogin" -> BSONDateTime(new org.joda.time.DateTime().getMillis())))) 
     update 
     } 
     result <- { 
     lazy val result = gotoLoginSucceeded(user.get.id.stringify) 
     result 
     } 
     } yield 
     if(user.isEmpty) BadRequest(views.html.logon(loginForm.withGlobalError("login\pass mismatch"))) 
     else if(!user.get.active) BadRequest(views.html.logon(loginForm.withGlobalError("inactive"))) 
     else if(update.err.isEmpty) result 
     else InternalServerError(views.html.logon(loginForm.withGlobalError("server error"))) 
     }) 

}

+3

Wie wäre es in mehrere kleinere Funktionen zu brechen? – vptheron

+0

Das sieht für mich ganz gut aus. Es könnte vielleicht von der Umgestaltung einiger dieser Blöcke in Methoden profitieren, wie EECOLOR es getan hat, aber ansonsten kann ich nichts falsches daran sehen. Was stört dich daran? –

Antwort

5

Ich würde wahrscheinlich den Code in so etwas wie dieses Refactoring:

def authenticate() = Action.async { implicit request => 
    loginForm.bindFromRequest.fold(
    hasErrors = displayFormWithErrors, 
    success = loginUser) 
} 

private def displayFormWithErrors[T](errors:Form[T]) = 
    Future.successful(BadRequest(views.html.logon(errors))) 

private def loginUser(userData:(String, String)) = { 
    val (username, password) = userData 

    findUser(username, password) 
    .flatMap { 
     case None => 
     showLoginFormWithError("user/pass incorect!!!") 
     case Some(user) if (!user.active) => 
     showLoginFormWithError("inactive!!!") 
     case Some(user) => 
     updateUserAndRedirect(user) 
    } 
} 

private def findUser(username:String, password:String) = 
    this.collection 
    .find(BSONDocument("name" -> username)) 
    .one[Account] 
    .map(_.filter(_.password == hashedPass(password, username))) 

private def showLoginFormWithError(error:String) = 
    Future.successful(BadRequest(
    views.html.logon(loginForm.withGlobalError(error)))) 

private def updateUserAndRedirect(user:Account) = 
    updateLastLogin(user) 
    .flatMap(_ => gotoLoginSucceeded(user.id.stringify)) 

private def updateLastLogin(user:Account) = 
    collection 
    .update(BSONDocument("_id" -> user.id), 
       BSONDocument("$set" -> 
       BSONDocument("lastLogin" -> 
       BSONDateTime(new JodaDateTime().getMillis())))) 
+0

Sieht aus wie meine erste Lösung. – sh1ng

0

Ich ziehe Passwort & Benutzerüberprüfung in den Formularvalidierung Klauseln zu tun - so etwas wie dieses (nicht getestet, aber Sie erhalten die Idee) wäre:

private val loginForm = Form(
    mapping(
    "name" -> nonEmptyText, 
    "password" -> nonEmptyText 
){ 
    (name, password) => (
     this.collection.find(BSONDocument("name" -> name)).one[Account], 
     password) 
    }{ 
    data => Some((data._1.name, data._2)) 
    }.verifying(new Constraint(None, Seq())({ 
    data: (Option[Account], String) => data match { 
     case (Some(account: Account), _) if !account.active => Invalid(ValidationError("inactive")) 
     case (Some(account: Account), password) if account.password==hashedPass(account.name, password) => Valid 
     case _ => Invalid(ValidationError("login/pass mismatch")) 
    } 
    })) 
) 

Und dann wird der Controller viel einfacher:

def authenticate() = Action.async { implicit request => 
    loginForm.bindFromRequest.fold(
    errors => Future.successful(BadRequest(views.html.logon(errors))), 
    usersData =>{ 
     collection.update(BSONDocument("_id" -> usersData._1.id), 
         BSONDocument("$set" -> 
         BSONDocument("lastLogin" -> BSONDateTime(new org.joda.time.DateTime().getMillis())))) 
       .flatMap(x => gotoLoginSucceeded(user.id.stringify)) 

    } 
) 
} 
+0

Fehler beim Kompilieren. this.collection.find (BSONDocument ("name" -> name)). one [Account]: Future [Option [Konto]] – sh1ng

+0

Ich glaube nicht, dass DB-Interaktion etwas ist, das als Teil der Formularvalidierung sehr gut passt Und, wie sh1ng sagt, es wird nur funktionieren, wenn Sie einen synchronen/blockierenden db-Client verwenden. – johanandren