Last active
March 30, 2018 19:31
-
-
Save loicknuchel/faa974c3661351b73227 to your computer and use it in GitHub Desktop.
Scala for-comprehension
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")) | |
} | |
} | |
} |
@vil1 no shame for promoting good libs :)
Your solution is really elegant and not that nerdy (as some scala code could be...).
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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) :
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 :Equipped with that, you would simply define
SecuredActionM
as