
This module offers Action Composition Utilities to Play2.1, 2.2, 2.3, 2.4, 2.5 applications
This module targets the Scala version of Play2.x
This module has been tested on Play2.5.0
Action Composition is somewhat limited in terms of composability.
For example, imagine that we want automatic DB transaction management, auth, and pjax functionality.
defTxAction(f: DBSession=>Request[AnyContent] =>Future[Result]):Action[AnyContent] = {
Action.async { request =>importTxBoundary.Future._DB localTx { session =>
f(session)(request)
}
}
}
defAuthAction(authority: Authority)(f: User=>DBSession=>Request[AnyContent] =>Future[Result]):Action[AnyContent] = {
TxAction { session => request =>valuser:Either[Result, User] = authorized(authority)(request)
user.right.map(u => f(u)(session)(request)).merge
}
}
typeTemplate=Html=>HtmldefPjaxAction(authority: Authority)(f: Template=>User=>DBSession=>Request[AnyContent] =>Future[Result]):Action[AnyContent] = {
AuthAction(authority) { user => session => request =>valtemplate=if (req.headers.keys("X-Pjax")) views.html.pjaxTemplate.apply else views.html.fullTemplate.apply
f(template)(user)(session)(request)
}
}defindex=PjaxAction(NormalUser) { template => user => session => request =>valmessages=Message.findAll(session)
Ok(views.hrml.index(messages)(template))
}So far so good, but what if we need a new action that does both DB transaction management and pjax?
We have to create another PjaxAction.
defPjaxAction(f: Template=>DBSession=>Request[AnyContent] =>Future[Result]):Action[AnyContent] = {
TxAction { session => request =>valtemplate=if (req.headers.keys("X-Pjax")) html.pjaxTemplate.apply else views.html.fullTemplate.apply
f(template)(session)(request)
}
}What a mess!
As an alternative, this module offers Composable Action composition using the power of traits.
First step, Create a sub trait of
StackableControllerfor every function.traitAuthElementextendsStackableControllerwithAuthConfigImpl { self: ControllerwithAuth=>caseobjectAuthKeyextendsRequestAttributeKey[User] caseobjectAuthorityKeyextendsRequestAttributeKey[Authority] overridedefproceed[A](req: RequestWithAttributes[A])(f: RequestWithAttributes[A] =>Future[Result]):Future[Result] = { (for { authority <- req.get(AuthorityKey).toRight(authorizationFailed(req)).right user <- authorized(authority)(req).right } yieldsuper.proceed(req.set(AuthKey, user))(f)).merge } implicitdefloggedIn(implicitreq: RequestWithAttributes[_]):User= req.get(AuthKey).get }
traitDBSessionElementextendsStackableController { self: Controller=>caseobjectDBSessionKeyextendsRequestAttributeKey[DBSession] overridedefproceed[A](req: RequestWithAttributes[A])(f: RequestWithAttributes[A] =>Future[Result]):Future[Result] = { importTxBoundary.Future._DB.localTx { session =>super.proceed(req.set(DBSessionKey, session))(f) } } implicitdefdbSession(implicitreq: RequestWithAttributes[_]):DBSession= req.get(DBSessionKey).get }
traitPjaxElementextendsStackableControllerwithAuthConfigImpl { self: ControllerwithAuth=>typeTemplate=Html=>HtmlcaseobjectTemplateKeyextendsRequestAttributeKey[Template] overridedefproceed[A](req: RequestWithAttributes[A])(f: RequestWithAttributes[A] =>Future[Result]):Future[Result] = { valtemplate=if (req.headers.keys("X-Pjax")) views.html.pjaxTemplate else views.html.fullTemplate super.proceed(req.set(TemplateKey, template))(f) } implicitdeftemplate(implicitreq: RequestWithAttributes[_]):Template= req.get(TemplateKey).get }
mix your traits into your Controller
objectApplicationextendsControllerwithPjaxElementwithAuthElementwithDBSessionElementwithAuthwithAuthConfigImpl { defmessages=StackAction(AuthorityKey->NormalUser) { implicit req =>valmessages=Message.findAll Ok(html.messages(messages)(loggedIn)(template)) } defeditMessage(id: MessageId) =StackAction(AuthorityKey->Administrator) { implicit req =>valmessages=Message.findAll Ok(html.messages(messages)(loggedIn)(template)) } }
Mixin different combinations of traits, depending on the functionality that you need.
objectNoAuthControllerextendsControllerwithPjaxElementwithDBSessionElement { defmessages=StackAction { implicit req =>valmessages=Message.findAll Ok(html.messages(messages)(GuestUser)(template)) } defeditMessage(id: MessageId) =StackAction { implicit req =>valmessages=Message.findAll Ok(html.messages(messages)(GuestUser)(template)) } }
Add a dependency declaration into your Build.scala or build.sbt file:
libraryDependencies +="jp.t2v"%%"stackable-controller"%"0.6.0"- for Play2.2.x, use 0.3.0
- for Play2.3.x, use 0.4.1
- for Play2.4.x, use 0.5.1
When you want to use scala.concurrent.ExecutionContext, you can use StackActionExecutionContext method.
StackActionExecutionContext returns an ExecutionContext that is given when StackAction method calling.
So, users of your StackElement can customize ExecutionContext.
traitAsyncElementextendsStackableControllerwithAuthConfigImpl {
self: ControllerwithAuth=>privatecaseobjectFooKeyextendsRequestAttributeKey[Foo]
overridedefproceed[A](req: RequestWithAttributes[A])(f: RequestWithAttributes[A] =>Future[Result]):Future[Result] = {
valctx:ExecutionContext=StackActionExecutionContext(req)
valfuture:Future[Foo] = getFooAsynchronously(ctx)
future flatMap { foo =>super.proceed(req.set(FooKey, foo))(f)
} recoverWith {
_ =>super.proceed(req)(f)
}
}
implicitdeffoo(implicitreq: RequestWithAttributes[_]):Foo= req.get(FooKey).get
}This library is released under the Apache Software License, version 2, which should be included with the source in a file named LICENSE.