§Custom Routing
Play provides a mechanism to bind types from path or query string parameters.
§PathBindable
PathBindable allows to bind business objects from the URL path; this means we’ll be able to define routes like /user/3
to call an action such as the following:
controller
def user(user: User) = Action {
Ok(user.name)
}
The user
parameter will automatically be retrieved using the id extracted from the URL path, e.g. with the following route definition:
/conf/routes
GET /user/:user controllers.BinderApplication.user(user: scalaguide.binder.models.User)
You can provide an implementation of PathBindable[A]
for any type A you want to be able to bind directly from the request path. It defines abstract methods bind
(build a value from the path) and unbind
(build a path fragment from a value).
For a class definition:
case class User(id: Int, name: String) {}
A simple example of the binder’s use binding the :id
path parameter:
implicit def pathBinder(implicit intBinder: PathBindable[Int]): PathBindable[User] = new PathBindable[User] {
override def bind(key: String, value: String): Either[String, User] = {
for {
id <- intBinder.bind(key, value)
user <- User.findById(id).toRight("User not found")
} yield user
}
override def unbind(key: String, user: User): String = {
user.id.toString
}
}
In this example findById method is invoked to retrieve User
instance; note that in real world such method should be lightweight and not involve e.g. DB access, because the code is called on the server IO thread and must be totally non-blocking.
You would therefore for example use simple objects identifier as path bindable, and retrieve the real values using action composition.
§QueryStringBindable
A similar mechanism is used for query string parameters; a route like /age
can be defined to call an action such as:
controller
def age(age: AgeRange) = Action {
Ok(age.from.toString)
}
The age
parameter will automatically be retrieved using parameters extracted from the query string e.g. /age?from=1&to=10
You can provide an implementation of QueryStringBindable[A]
for any type A you want to be able to bind directly from the request query string. Similar to PathBindable
, it defines abstract methods bind
and unbind
.
For a class definition:
case class AgeRange(from: Int, to: Int) {}
A simple example of the binder’s use binding the :from
and :to
query string parameters:
implicit def queryStringBindable(implicit intBinder: QueryStringBindable[Int]): QueryStringBindable[AgeRange] =
new QueryStringBindable[AgeRange] {
override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, AgeRange]] = {
for {
from <- intBinder.bind("from", params)
to <- intBinder.bind("to", params)
} yield {
(from, to) match {
case (Right(from), Right(to)) => Right(AgeRange(from, to))
case _ => Left("Unable to bind an AgeRange")
}
}
}
override def unbind(key: String, ageRange: AgeRange): String = {
intBinder.unbind("from", ageRange.from) + "&" + intBinder.unbind("to", ageRange.to)
}
}
All binders Play provides automatically apply form URL encoding in their unbind
methods, so all special characters are safely URL encoded. This doesn’t happen automatically however when implementing custom binders, therefore make sure to encode key/value parts if necessary:
override def unbind(key: String, cartItem: CartItem): String = {
// If we don't use Play's QueryStringBindable[String].unbind() for some reason, we need to construct the result string manually.
// The key is constant and does not contain any special character, but
// value may contain special characters => need form URL encoding for cartItem.identifier:
"identifier=" + URLEncoder.encode(cartItem.identifier, "utf-8")
}
Next: Extending Play