Reputation: 798
All my actions have similar structure:
class MyController @Inject() (
val cc: ControllerComponent)
extends AbstractController(cc) {
def get(name: String) = Action { request =>
try {
commonCode(request) // Creates state
actionSpecificCode(request) // Uses state
}
catch {
case mx: MyException =>
mx.toResult()
case t: Throwable =>
// convert to BadRequest
}
}
}
I'd like to factor out the try-catch block together with the commonBlock()
into a custom action so that the improved code would look like
class MyController @Inject() (
val myAction: MyAction,
val cc: ControllerComponent)
extends AbstractController(cc) {
def get(name: String) = myAction { request =>
actionSpecificCode(request)
}
}
Which is easy enough to do, using custom action composition, e.g.
override def invokeBlock[A](
request: Request[A],
block: (Request[A]) => Future[Result]) = { request =>
// try block
}
The wrinkle is that the commonCode()
inside the try block needs to create state accessible inside actionSpecificCode()
.
The simpleminded way to do it would be to keep the state in private fields of MyAction, which then would be accessible inside the controller methods as myAction.field
, but that doesn't work because the same action instance is used by multiple concurrent requests.
The next idea (and one that lead me to migrate to Play 2.6, which implements request attributes) was to do what any Servlet application would do: store request-local state in request attributes. That also ran aground, when I discovered that requests themselves are not mutable and adding an attribute to a request leaves my code holding the new request which I have no way of passing down the action inheritance chain.
(This is in follow up to this original question)
Upvotes: 0
Views: 83
Reputation: 3514
This seems like a typical usage for playframework's ActionRefiner
. The doc describes how to use those to change the request type (and add some state to those). I think that instead of changing the request type, you can simply add request attributes (as you suggested) using, for example, an ActionTransformer
.
The strength of this approach comes when composing refiners, which is exactly what you are trying to achieve. An illustration is given on the same doc page, and in your case this could become:
class MyController @Inject() (
val myAction: MyAction,
val cc: ControllerComponent)
extends AbstractController(cc) {
def commonCode = new ActionRefiner[Request, Request] {
def refine[A](input: Request[A]) = Future.successful {
// code taken from your other question
val user: User = input.attrs(Attrs.User)
input.addAttr(Attrs.User, newUser)
}
}
def get(name: String) = (Action andThen commonCode) { request =>
actionSpecificCode(request)
}
}
Upvotes: 2
Reputation: 6182
Have commonCode
return the new request (with all the new state) and then pass that along to the actionSpecificCode
, which in the context of a custom action would be the block
parameter to the invokeBlock
:
override def invokeBlock[A](
request: Request[A],
block: (Request[A]) => Future[Result]
) = { request =>
try {
val newRequest = commonCode(request) // Creates state
block(newRequest) // Uses state
}
catch {
...
}
}
Even though the request is not mutable, every action/filter has a chance to replace it with a modified version that gets passed to the next handler on the chain.
Upvotes: 2