2014-02-11 3 views
6

Простой код, который должен проверять пользователя по пропуску, пользователь активен и после этого последнего обновления последнего времени datetime.Переписывание кода асинхронного/обратного вызова Scala

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)) 

       } 
       }) 
      }) 
    } 

Как переписать его на менее плоские карты/карты спагетти?

Другое решение

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

Как насчет разбить его на несколько более мелких функций? – vptheron

+0

Это выглядит как отличный код для меня. Возможно, вам удастся реорганизовать некоторые из этих блоков в методы, как это сделала EECOLOR, но в противном случае я не вижу в этом ничего плохого. Что это беспокоит вас об этом –

ответ

5

я бы, вероятно, реорганизовать код во что-то вроде этого:

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

Похоже, мое первое решение. – sh1ng

0

Я предпочитаю делать пароль & проверки пользователей в пунктах проверки формы - было бы что-то вроде этого (непроверенные, но вы получите идею):

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")) 
    } 
    })) 
) 

И тогда контроллер становится намного проще:

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

Ошибка компиляции. this.collection.find (BSONDocument ("name" -> name)). one [Account]: Future [Option [Account]] – sh1ng

+0

Я не думаю, что взаимодействие с БД - это то, что очень хорошо подходит как часть проверки формы , а также, поскольку sh1ng говорит, что он будет работать, только если вы используете синхронный/блокирующий db-клиент. – johanandren

Смежные вопросы