-
-
Save loicknuchel/faa974c3661351b73227 to your computer and use it in GitHub Desktop.
object Sessions extends SilhouetteEnvironment { | |
def details(eventId: String, sessionId: String) = SecuredAction.async { implicit req => | |
implicit val user = req.identity | |
val res: Future[Result] = for { | |
eventOpt: Option[Event] <- EventRepository.getByUuid(eventId) | |
sessionOpt: Option[Session] <- SessionRepository.getByUuid(sessionId) | |
} yield { | |
var res2: Option[Result] = for { | |
event: Event <- eventOpt | |
session: Session <- sessionOpt | |
} yield { | |
Ok(backend.views.html.Events.Sessions.details(session, List(), event)) | |
} | |
res2.getOrElse { NotFound(views.html.error("404", "Event not found...")) } | |
} | |
res | |
} | |
def doUpdate(eventId: String, sessionId: String) = SecuredAction.async { implicit req => | |
implicit val user = req.identity | |
val res: Future[Future[Result]] = for { | |
eventOpt: Option[Event] <- EventRepository.getByUuid(eventId) | |
sessionOpt: Option[Session] <- SessionRepository.getByUuid(sessionId) | |
} yield { | |
val res2: Option[Future[Result]] = for { | |
event: Event <- eventOpt | |
session: Session <- sessionOpt | |
} yield { | |
createForm.bindFromRequest.fold( | |
formWithErrors => Future(BadRequest(views.html.Sessions.update(formWithErrors, session, event))), | |
formData: Session => SessionRepository.update(sessionId, formData).map { err: LastError => | |
Redirect(controllers.routes.Sessions.details(eventId, sessionId)) | |
}) | |
} | |
res2.getOrElse(Future(NotFound(views.html.error("404", "Event not found...")))) | |
} | |
res.flatMap(identity) | |
} | |
} |
object Sessions extends SilhouetteEnvironment { | |
def details(eventId: String, sessionId: String) = SecuredAction.async { implicit req => | |
implicit val user = req.identity | |
val res: Future[Result] = for { | |
Some(event: Event) <- EventRepository.getByUuid(eventId) | |
Some(session: Session) <- SessionRepository.getByUuid(sessionId) | |
} yield { | |
Ok(backend.views.html.Events.Sessions.details(session, List(), event)) | |
} | |
// throws 'NoSuchElementException: Future.filter predicate is not satisfied' on None :( | |
res.recover { | |
case NoSuchElementException => Future(NotFound("Not found")) | |
} | |
} | |
def doUpdate(eventId: String, sessionId: String) = SecuredAction.async { implicit req => | |
implicit val user = req.identity | |
val res: Future[Future[Result]] = for { | |
Some(event: Event) <- EventRepository.getByUuid(eventId) | |
Some(session: Session) <- SessionRepository.getByUuid(sessionId) | |
} yield { | |
createForm.bindFromRequest.fold( | |
formWithErrors => Future(BadRequest(views.html.Sessions.update(formWithErrors, session, event))), | |
formData: Session => SessionRepository.update(sessionId, formData).map { err: LastError => | |
Redirect(controllers.routes.Sessions.details(eventId, sessionId)) | |
}) | |
} | |
res.flatMap(identity).recover { | |
case NoSuchElementException => Future(NotFound("Not found")) | |
} | |
} | |
} |
object Sessions extends SilhouetteEnvironment { | |
def details(eventId: String, sessionId: String) = SecuredAction.async { implicit req => | |
implicit val user = req.identity | |
withEvent(eventId) { event => | |
withSession(sessionId) { session => | |
Ok(backend.views.html.Events.Sessions.details(session, List(), event)) | |
} | |
} | |
} | |
def doUpdate(eventId: String, sessionId: String) = SecuredAction.async { implicit req => | |
implicit val user = req.identity | |
withEvent(eventId) { event => | |
withSession(sessionId) { session => | |
createForm.bindFromRequest.fold( | |
formWithErrors => Future(BadRequest(views.html.Sessions.update(formWithErrors, session, event))), | |
formData: Session => SessionRepository.update(sessionId, formData).map { err: LastError => | |
Redirect(controllers.routes.Sessions.details(eventId, sessionId)) | |
}) | |
} | |
} | |
} | |
def withEvent(eventId: String)(block: Event => Future[Result]): Future[Result] = { | |
EventRepository.getByUuid(eventId).flatMap { | |
case Some(event: Event) => block(event) | |
case None => Future(NotFound("Event not found")) | |
} | |
} | |
def withSession(sessionId: String)(block: Session => Future[Result]): Future[Result] = { | |
SessionRepository.getByUuid(sessionId).flatMap { | |
case Some(session: Session) => block(session) | |
case None => Future(NotFound("Session not found")) | |
} | |
} | |
} |
Hi, sorry to be so late to the party.
So, using (sorry for the self promotion) https://github.com/Kanaka-io/play-monadic-actions, your example would look something like (typing this directly here, with the help of no compiler, please excuse the probable compile errors) :
object Sessions extends SilhouetteEnvironment with MonadicActions {
def details(eventId: String, sessionId: String) = SecuredActionM { implicit req =>
implicit val user = req.identity
for {
event <- EventRepository.getByUuid(eventId) ?| NotFound(s"no event found with Id $eventId")
session <- SessionRepository.getByUuid(sessionId) ?| NotFound(s"no session found with Id $sessionId")
} yield Ok(backend.views.html.Events.Sessions.details(session, List(), event))
}
def doUpdate(eventId: String, sessionId: String) = SecuredActionM { implicit req =>
implicit val user = req.identity
for {
event <- EventRepository.getByUuid(eventId) ?| NotFound(s"no event found with Id $eventId")
session <- SessionRepository.getByUuid(sessionId) ?| NotFound(s"no session found with Id $sessionId")
formData <- createForm.bindFromRequest ?| (err => BadRequest(views.html.Sessions.update(err, session, event))
_ <- SessionRepository.update(sessionId, formData) ?| (throwable => InternalServerError("unable to update session : ${throwable.getMessage}"))
} yield Redirect(controllers.routes.Sessions.details(eventId, sessionId))
}
I may have (lazily) forgotten to render errors using a template, but an interesting outcome of this mechanic translation is the discovery that the incidental complexity of the vanilla-scala solution made you forget some error cases that were not properly handled in your example. Using for-comprehensions along with the ?|
operator forces you to think about every little edge case (otherwise it doesn't compile) which, after about a year of continued usage, I feel very helpful, since us careless programmers have quite a hard time thinking outside the happy path.
This goodness (IMHO) comes with a little price : you've got to write that strange SecuredActionM
(the M stands for "monadic"). This is not a big deal, and should definitely be part of the lib at some point, but for the time being, you would need something like :
package object controllers {
def constructPlayResult(result: HttpResult[Result])(implicit ec: ExecutionContext) = result.run.map {
_.toEither.merge
}
import scala.language.higherKinds
case class MonadicAction[R[_]](inner: ActionFunction[Request, R]) extends ControllerUtils {
def apply[A](bodyParser: BodyParser[A])(block: R[A] => HttpResult[Result]) = new Action[A] {
override def parser = bodyParser
override def apply(request: Request[A]) = inner.invokeBlock(request, (req: R[A]) => constructPlayResult(block(req)))
}
def apply(block: R[AnyContent] => HttpResult[Result]): Action[AnyContent] = apply(BodyParsers.parse.anyContent)(block)
}
}
Equipped with that, you would simply define SecuredActionM
as
val SecuredActionM = MonadicAction(SecuredAction)
@vil1 no shame for promoting good libs :)
Your solution is really elegant and not that nerdy (as some scala code could be...).
Pour les exception c'est parce que c'est ce que j'avais lu de la proposition de @StudioDev.
Pour le code réutilisable, si tu me dis que ce n'est pas un critère important, je ne peux rien faire pour toi :) Surtout quand ça coute même pas 3 ligne de code.
Ensuite le notation imbriquée, dès que tu dépasses 2 appels de service ça devient complètement illisible alors qu'avec une notation for-yield tu écris du code impératif très lisible.