Play 2.6.x compose ActionBuilder andThen ActionRefiner

1,077 views
Skip to first unread message

Fernando Aspiazu

unread,
Jul 24, 2017, 12:30:11 PM7/24/17
to Play Framework
Hi all,

I'm having some troubles on composing Actions with ActionRefiners.

Example:

def doSomething: (UserAction andThen UserActionRefiner).async { request => ... }

In the example above, the expression
(UserAction andThen UserActionRefiner)
has type
ActionFunction[-R, +P]

So async function is not a member of that ActionFunction, but I need to make only that kind of composition.

Do I need to compose those actions in other way? Or is there anything missing?

Thanks in advance.

br...@iterable.com

unread,
Jul 24, 2017, 5:06:50 PM7/24/17
to Play Framework
Fernando-

I found the following article very useful when first learning how to work with Play's action composition: http://www.tzavellas.com/techblog/2015/02/10/action-composition-in-play-framework/

What you need to do is to work with `ActionBuilder` instances to compose your action.  `ActionBuilder` is the class that provides the `async` method that you are trying to call.  To paraphrase the article linked above, when you define a new `ActionRefiner` (or `ActionFilter` or `ActionTransformer` for that matter) the suggested approach is to mix those traits into an `ActionBuilder` so that you have a handy extension point (`refine` in your case) but also all of the convenient methods for action building provided in `ActionBuilder`.

Hopefully that's enough to get you started down the right path...

-Brian

Jevin G

unread,
Jul 25, 2017, 2:06:14 AM7/25/17
to Play Framework
Great article! 

I'm new to the Play Framework and was wondering how the snippet of logging code linked in the article would look in 2.6. 

The 2.6 migration guide states that we should pass in a body parser however it doesn't seem like you can inject an instance of the default body parser into the LoggedAction object.


import scala.concurrent.Future
import play.api.Logger
import play.api.mvc._

object LoggedAction extends ActionBuilder[Request] {

 
def invokeBlock[A](
      request
: Request[A],
      block
: Request[A] => Future[Result]): Future[Result] = {
   
Logger.debug(s"HTTP request: ${request.method} ${request.uri}")
    block
(request)
 
}
}

Enter code here...


- Jevin

Greg Methvin

unread,
Jul 25, 2017, 4:54:17 AM7/25/17
to play-framework
You just need to somehow provide a value for the abstract methods in LoggedAction. If you want LoggedAction to stay an object, you can place your LoggedAction inside a class/trait that already has the body parsers available, for example:

trait MyActions { self: BaseController =>
  object LoggedAction extends ActionBuilder[Request, AnyContent] {
    override def parser: BodyParser[AnyContent] = self.parse.default
    override protected def executionContext: ExecutionContext = self.defaultExecutionContext
    // ...
  }
}

Then you'd just mix in MyActions into your controller. The above example should work for any controller extending BaseController (including AbstractController and InjectedController). The helper trait is a good way if you have lots of different kinds of actions you want to use in the same controller.

Converting LoggedAction to a class and adding a constructor dependency on BodyParsers.Default will work as well:

class LoggedAction @Inject()(val parser: BodyParsers.Default, val executionContext: ExecutionContext)
  extends ActionBuilder[Request, AnyContent] {
  // ...
}

Then you would simply inject a `loggedAction: LoggedAction` into your constructor.

Play doesn't have an opinion on which way you choose.

--
You received this message because you are subscribed to the Google Groups "Play Framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/play-framework/e7a9c2bf-d4c9-46f3-a9f1-4ff14b5ada80%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Greg Methvin
Tech Lead - Play Framework

Jevin G

unread,
Jul 26, 2017, 12:20:24 AM7/26/17
to Play Framework
Thank you! I got a working demo - 

trait Logging { self: BaseController =>

object LoggedAction extends ActionBuilder[Request, AnyContent]{

    override def parser = self.parse.default


override protected def executionContext: ExecutionContext = self.defaultExecutionContext

    override def invokeBlock[A](request: Request[A],
                                block: Request[A] => Future[Result]): Future[Result] = {
      Logger.debug(s"Received request for ${request.path}")
block(request)
}
}
}

Perhaps we should include it in the docs? 

Cheers,

Jevin
To unsubscribe from this group and stop receiving emails from it, send an email to play-framewor...@googlegroups.com.

Greg Methvin

unread,
Jul 29, 2017, 3:26:15 PM7/29/17
to Play Framework
Hi Jevin,

If you're interested in submitting a PR to improve the docs that would be great :)

Greg
Reply all
Reply to author
Forward
0 new messages