Alvaro Carrasco
Alvaro Carrasco

Reputation: 6182

In Play, how do I include custom query parameters in a reverse-routed URL? (Call)

With a route like:

GET /users/:id   controllers.Users.view(id)

In the controller, I can read a custom backTo query string param like this:

def view (id: String) = Action {r =>
  val backTo = r.getQueryString("backTo") // read custom param
  ...
}

Assuming backTo is an optional query string parameter that I don't want to include in the routes definition (maybe all the actions can read it).

How do I construct a URL using reverse-routing that includes the backTo parameter?

I would expect something like:

routes.Users.view(id).withQueryString("backTo" -> Seq("previous"))

But that doesn't exist.

Upvotes: 2

Views: 1830

Answers (3)

Alvaro Carrasco
Alvaro Carrasco

Reputation: 6182

I ended up just pimping a withQueryString method onto Call that just parses the URL and rebuilds the URL with the extra parameters:

implicit class CallOps (c: Call) {
  import org.jboss.netty.handler.codec.http.{QueryStringDecoder, QueryStringEncoder}
  import scala.collection.JavaConverters._

  def withQueryString(query: (String,Seq[String])*): Call = {
    val decoded = new QueryStringDecoder(c.url)
    val newUrl = new QueryStringEncoder(decoded.getPath)
    val params = decoded.getParameters.asScala.mapValues(_.asScala.toSeq).toSeq
    for {
      (key, values) <- params ++ query
      value <- values
    } newUrl.addParam(key, value)
    Call(c.method, newUrl.toString, c.fragment)
  }
}

And now I can use it like this:

routes.Users.view(id).withQueryString("backTo" -> Seq("previous")).url

I wish I didn't have to re-parse the URL, but by the time I have a Call, the URL has already been constructed.

Upvotes: 2

biesior
biesior

Reputation: 55798

What's wrong about using Parameters with default values (without resolving it manually by yourself)? sample:

def view(id: String, backTo: String, forwardTo: String, whatever: String) = Action {
  Ok(
    "ID: " + id
      + ", backTo: " + backTo
      + ", forwardTo: " + forwardTo
      + ", whatever: " + whatever
  )
}

route:

GET  /users/:id  controllers.Users.view(id, backTo ?= null, forwardTo ?= null, whatever ?= null)

it allows you to make reverse routes with only ID as a required param:

// /users/john-doe
routes.Users.view("john-doe")

by ordered params:

// /users/john-doe?backTo=prev&forwardTo=next&whatever=bar
routes.Users.view("john-doe", "prev", "next", "bar")

or only with named optional params:

// /users/john-doe?whatever=baz
routes.Users.view("john-doe", whatever = "baz")

What's more important it's type-safe as it's absolutely under the Play's routing syntax, without any manual manipulation. Also you don't need to care if you should start your params with ? or & char: "?backTo=home" vs. "&backTo=home"...

Upvotes: 1

Anton Sarov
Anton Sarov

Reputation: 3748

I think you would have to come down to String level as there is really no such method available (yet).

What the reverse router is giving you is actually a play.api.mvc.Call object. You can take a look at the source here:

https://github.com/playframework/playframework/blob/2.5.0/framework/src/play/src/main/scala/play/api/mvc/Http.scala#L359

You will see that you can get the absoluteURL() - which returns you a String - from here you would have to manipulate it by adding your query parameter:

val url = routes.Users.view(id).absoluteURL() + "?backTo=home"

Upvotes: 1

Related Questions