How to send a parameter to a custom action? - scala

I am using action composition of play framework 2.3 and I would like to send parameters to the custom action.
For example, if you have a custom action that adds a cache how the custom action can receive the cache key and the desired cache time. Example code inside a play controller:
def myAction(p1: String) = CachedAction(key="myAction1"+p1, time = 300.seconds) {
implicit request =>
... do an expensive calculation …
Ok(views.html.example.template())
}
I have tested an ActionBuilder combined with a custom request but I haven't found a solution.
I am aware that play offers a cache for actions, unfortunately that cache does not meet all the requirements.

I am not sure about solution with ActionBuilder or ActionRefiner but that may work for you:
def CachedAction(key: String, time: Duration)(f: Request[AnyContent] => Result) = {
Action{ request =>
if(cache.contains(key)){
...
} else{
...
}
f(request)
}
}
and then:
def myAction(p1: String) = CachedAction("hello" + p1, 100 millis){ request =>
Ok("cached action")
}
Edit:
Since you need Action.async you can write something like that:
case class Caching[A](key: String, time: Duration)(action: Action[A]) extends Action[A] {
def apply(request: Request[A]): Future[Result] = {
if(cache.contains(key)){
Logger.info("Found key: " + key)
}
action(request)
}
lazy val parser = action.parser
}
def cached(p1: String) = Caching(key = p1, time = 100 millis){
Action.async { Future(Ok("hello")) }
//or
Action { Ok("hello") }
}
Case class with two parameter lists looks weird but it works.
Docs:https://www.playframework.com/documentation/2.3.x/ScalaActionsComposition#Composing-actions

Related

Is it possible to access the request body as a JsValue in action composition functions in Play Framework 2.8?

In the Play documentation the Request type is generic [A] for action composition functions.
I am trying to add a custom query object to every endpoint that is responsible for querying data, to do that I need to grab the request body as JsValue and parse it into an appropriate case class. I cant seem to add a type to the WrappedRequest or the refine method in the action, and I cant do request.body.as[JsValue] in the refine method. Is there a way accomplish the following?
I would like to do something like this with action composition in endpoints that handle querying data:
def SomeEndpointWithAQuery() = auth.protectedAction(parse.json).andThen(QueryAction)) { request:QueryRequest[JsValue] =>
val query = request.queryObject
res = // Do query stuff
Ok(res)
}
QueryRequest is a WrappedRequest that provides a query object:
class QueryRequest[A](val query: QueryParser, request: AuthUserRequest[A]) extends WrappedRequest[A](request) {
def queryObject = query.parseQuery()
}
object QueryActionObject {
def QueryAction()(implicit ec: ExecutionContext): ActionRefiner[AuthUserRequest, QueryRequest] = new ActionRefiner[AuthUserRequest, QueryRequest] {
def executionContext: ExecutionContext = ec
def refine[A](input: AuthUserRequest[A]) = Future.successful {
val qp = new QueryParser(input.body.as[JsObject])
if (qp.validate()) {
Right(new QueryRequest(qp, input))
} else {
// bad request handling ...
}
}
}
}
auth.protectedAction is an actionBuilder that adds user info based on request.session info, it shouldnt affect how this queryAction works.

play framework 2.5 move Action.async logic from controller to service

I have a controller that has many business logic, I would like to move the code inside Action.async block of code. This code works, how can I move to another class(service) the code that is inside of the Action.async?:
def tweetsnew(query: String) = Action.async {
// Move From Here...
credentials.map {
case (consumerKey, requestToken) =>
ws.url("https://api.twitter.com/1.1/search/tweets.json")
.sign(OAuthCalculator(consumerKey, requestToken))
.withQueryString("q" -> query)
.withQueryString("max_id" -> "833342796736167936")
.get().map { twitterResponse =>
if (twitterResponse.status == 200) {
// Here There Are More Complex Logic
Ok("That is fine: "+twitterResponse.body)
} else {
throw new Exception(s"Could not retrieve tweets for $query query term")
}
}
}.getOrElse {
Future.failed(new Exception("You did not correctly configure the Twitter credentials"))
}
//....To Here. To Another Class
}
I have been checked the docummentation, something related to create a Future[Result] but I am not be able to make that the function returns the same type that Action.async expect.
Action.async expects a return type of Future[Result] so you need to create a function that returns a Future[Result]
First step, extract your code to a function:
object TwitterService {
def search(query: String, consumerKey: ConsumerKey, requestToken: RequestToken)(implicit ws: WSClient, ec: ExecutionContext): Future[Result] = {
// your code that make the ws call that returns Ok("...")
}
}
Then in the controller call your function:
def tweetsnew(query: String) = Action.async {
credentials.map {
case (consumerKey, requestToken) => TwitterService.search(query, consumerKey, requestToken)
}.getOrElse {
// Better to send a bad request or a redirect instead of an Exception
Future.successful(BadRequest("Credentials not set"))
}
}

Play Action Composition - hardcoding parameterized parser

I would like to create a custom action which takes away the boilerplate of writing actions like this:
Action[MyClass](BodyParsers.parse.json[MyClass]) { req => ...
However, I keep running into class definition errors. Here has been my most successful attempt:
class JsonAction[A: Reads] extends ActionBuilder[Request] {
def hardcodedJson[A: Reads](action: Action[A]) =
Action.async(BodyParsers.parse.json[A]) { request => action(request) }
def invokeBlock[A: Reads](request: Request[A], block: (Request[A]) => Future[Result]) = {
block(request)
}
override def composeAction[A: Reads](action: Action[A]) = hardcodedJson(action)
}
but I get the following error: method composeAction overrides nothing.
If I change composeAction[A: Reads] to composeAction[A] it tells me there isn't a Json Serializer for type A.
Is there some other way to define this custom action?
Yep, I tried to get this to work with the official ActionBuilder way-of-doing-it and just could not get the types to line up.
Here's something that worked for me though:
object JsonActionHelper {
def withReads[A](act: Request[A] => Future[Result])(implicit reads:Reads[A]) =
Action.async(BodyParsers.parse.json(reads))(act)
}
Usage in your controller (FooJson is an object containing an implicit Reads[Foo]):
import models.FooJson._
import JsonActionHelper._
def handleIncomingFoo(fooId:String) = withReads[Foo] { req =>
val foo:Foo = req.body
...
Ok(...)
}
ActionBuilder is not generic enough for your use-case; there's nowhere for you to pass in your Reads[T].
There's nothing special about ActionBuilder though. It's a collection of apply and async factory methods. You can define your own Action type with whatever factory methods you need:
object JsonAction {
def apply[A : Reads](request: Request[A] => Result) = Action(BodyParsers.parse.json[A])(request)
}
// these are equivalent:
Action[MyClass](BodyParsers.parse.json[MyClass]) { req => ??? }
JsonAction[MyClass] { req => ??? }

I18n in Play Framework 2.4.0

Here is my routes file:
GET /:lang controller.Application.index(lang: String)
GET /:lang/news controller.Application.news(lang: String)
Note that all of them start with /:lang.
Currently, I write Application.scala as
def index(lang: String) = Action {
implicit val messages: Messages = play.api.i18n.Messages.Implicits.applicationMessages(
Lang(lang), play.api.Play.current)
Ok(views.html.index("title"))
}
In this way, I have to write as many implicit Messages as Action. Is there any better solution for this?
Passing just Lang is simpler option:
def lang(lang: String) = Action {
Ok(views.html.index("play")(Lang(lang)))
}
//template
#(text: String)(implicit lang: play.api.i18n.Lang)
#Messages("hello")
You can reuse some code by using action composition, define wrapped request and action:
case class LocalizedRequest(val lang: Lang, request: Request[AnyContent]) extends WrappedRequest(request)
def LocalizedAction(lang: String)(f: LocalizedRequest => Result) = {
Action{ request =>
f(LocalizedRequest(Lang(lang), request))
}
}
Now you are able to reuse LocalizedAction like this:
//template
#(text: String)(implicit request: controllers.LocalizedRequest)
#Messages("hello")
//controller
def lang(lang: String) = LocalizedAction(lang){implicit request =>
Ok(views.html.index("play"))
}
Finally, I solved this problem in the following way.
As #Infinity suggests, I defined wrapped request and action as:
case class LocalizedRequest(messages: Messages,
request: Request[AnyContent])
extends WrappedRequest(request)
object Actions {
def LocalizedAction(lang: String)(f: LocalizedRequest => Result) = {
Action { request =>
f(LocalizedRequest(applicationMessages(Lang(lang), current), request))
}
}
object Implicits {
implicit def localizedRequest2Messages(implicit request: LocalizedRequest): Messages = request.messages
}
}
Now I'm able to use LocalizedAction like this:
def lang(lang: String) = LocalizedAction(lang) { implicit request =>
Ok(views.html.index("play"))
}
However, in order to omit the implicit parameter of Messages, which should be a play.api.i18n.Messages, I added a line to my template as:
#import controllers.Actions.Implicits._

Play Framework 2.2 action composition returning a custom object

I am trying to create a custom play.api.mvc.Action which can be used to populate a CustomerAccount based on the request and pass the CustomerAccount into the controller.
Following the documentation for Play 2.2.x I've created an Action and ActionBuilder but I cannot seem to return the CustomerAccount from within the action.
My current code is:
case class AccountWrappedRequest[A](account: CustomerAccount, request: Request[A]) extends WrappedRequest[A](request)
case class Account[A](action: Action[A]) extends Action[A] {
lazy val parser = action.parser
def apply(request: Request[A]): Future[SimpleResult] = {
AccountService.getBySubdomain(request.host).map { account =>
// Do something to return the account like return a new AccountWrappedRequest?
action(AccountWrappedRequest(account, request))
} getOrElse {
Future.successful(NotFound(views.html.account_not_found()))
}
}
}
object AccountAction extends ActionBuilder[AccountWrappedRequest] {
def invokeBlock[A](request: Request[A], block: (AccountWrappedRequest[A]) => Future[SimpleResult]) = {
// Or here to pass it to the next request?
block(request) // block(AccountWrappedRequest(account??, request))
}
override def composeAction[A](action: Action[A]) = Account(action)
}
Note: This will not compile because the block(request) function is expecting a type of AccountWrappedRequest which I cannot populate. It will compile when using a straight Request
Additionally...
Ultimately I want to be able to combine this Account action with an Authentication action so that the CustomerAccount can be passed into the Authentication action and user authentication can be provided based on that customer's account. I would then want to pass the customer account and user into the controller.
For example:
Account(Authenticated(Action))) { request => request.account; request.user ... } or better yet as individual objects not requiring a custom request object.
I'm not sure if this is the best way to do it but I have managed to come up with a solution that seems to work pretty well.
The key was to match on the request converting it into an AccountWrappedRequest inside invokeBlock before passing it on to the next request. If another Action in the chain is expecting a value from an earlier action in the chain you can then similarly match the request converting it into the type you need to access the request parameters.
Updating the example from the original question:
case class AccountWrappedRequest[A](account: CustomerAccount, request: Request[A]) extends WrappedRequest[A](request)
case class Account[A](action: Action[A]) extends Action[A] {
lazy val parser = action.parser
def apply(request: Request[A]): Future[SimpleResult] = {
AccountService.getBySubdomain(request.host).map { account =>
action(AccountWrappedRequest(account, request))
} getOrElse {
Future.successful(NotFound(views.html.account_not_found()))
}
}
}
object AccountAction extends ActionBuilder[AccountWrappedRequest] {
def invokeBlock[A](request: Request[A], block: (AccountWrappedRequest[A]) => Future[SimpleResult]) = {
request match {
case req: AccountRequest[A] => block(req)
case _ => Future.successful(BadRequest("400 Invalid Request"))
}
}
override def composeAction[A](action: Action[A]) = Account(action)
}
Then inside the apply() method of another Action (the Authenticated action in my case) you can similarly do:
def apply(request: Request[A]): Future[SimpleResult] = {
request match {
case req: AccountRequest[A] => {
// Do something that requires req.account
val user = User(1, "New User")
action(AuthenticatedWrappedRequest(req.account, user, request))
}
case _ => Future.successful(BadRequest("400 Invalid Request"))
}
}
And you can chain the actions together in the ActionBuilder
override def composeAction[A](action: Action[A]) = Account(Authenticated(action))
If AuthenticatedWrappedRequest is then passed into the controller you would have access to request.account, request.user and all the usual request parameters.
As you can see there are a couple of cases where the response is unknown which would generate a BadRequest. In reality these should never get called as far as I can tell but they are in there just incase.
I would love to have some feedback on this solution as I'm still fairly new to Scala and I'm not sure if there might be a better way to do it with the same result but I hope this is of use to someone too.
I wrote a standalone small (ish) example that does what you're looking for:
https://github.com/aellerton/play-login-example
I gave up trying to use the Security classes that exist in the play framework proper. I'm sure they're good, but I just couldn't understand them.
Brief guide...
In the example code, a controller is declared as using the AuthenticatedRequests trait:
object UserSpecificController extends Controller with AuthenticatedRequests {
...
}
Forcing any page to require authentication (or redirect to get it) is done with the RequireAuthentication action:
def authenticatedIndex = RequireAuthentication { implicit request: AuthenticatedRequest[AnyContent] =>
Ok("This content will be accessible only after logging in)
}
Signing out is done by using the AbandonAuthentication action:
def signOut = AbandonAuthentication { implicit request =>
Ok("You're logged out.").withNewSession
}
Note that for this to work, you must override methods from the AuthenticatedRequests trait, e.g.:
override def authenticationRequired[A](request: Request[A]): Future[SimpleResult] = {
Future.successful(
Redirect(routes.LoginController.showLoginForm).withSession("goto" -> request.path)
)
}
There's more to it; best to see the code.
HTH
Andrew