Skip to content

Instantly share code, notes, and snippets.

@julienrf
Created April 9, 2012 16:20
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save julienrf/2344517 to your computer and use it in GitHub Desktop.
Save julienrf/2344517 to your computer and use it in GitHub Desktop.
How to implement a custom PathBindable with Play 2
def show(article: Article) = Action {
Ok(views.html.article(article))
}
case class Article(id: Long, name: String, price: Double)
implicit def articlePathBindable(implicit longBinder: PathBindable[Long]) = new PathBindable[Article] {
def bind(key: String, value: String): Either[String, Article] =
for {
id <- longBinder.bind(key, value).right
article <- Article.findById(id).toRight("Article not found").right
} yield article
def unbind(key: String, article: Article): String =
longBinder.unbind(key, article.id)
}
val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
routesImport += "binders._"
)
override def onBadRequest(request: RequestHeader, error: String) = error match {
case "Article not found" => Redirect(controllers.routes.Articles.list)
case _ => super.onBadRequest(request, error)
}
GET /show/:article controllers.Articles.show(article: models.Article)
@julienrf
Copy link
Author

QueryStringBindable[A] works basically the same way, but its bind method returns an Option[Either[String, A]]: if the parameter were not present in the query string it should return None, otherwise Some(Left(error)) in case of binding error or Some(Right(value)) in case of binding success.
What is interesting with QueryStringBindable is that you can pull several parameters from the query string to bind your value. E.g.:

case class Bounds(min: Int, max: Int)

object Bounds {
  implicit def binder(implicit intBinder: QueryStringBindable[Int]) = new QueryStringBindable[Bounds] {
    def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, Bounds]] = {
      for {
        minE <- intBinder.bind(key + ".min", params)
        maxE <- intBinder.bind(key + ".max", params)
      } yield {
        (minE, maxE) match {
          case (Right(min), Right(max)) if min <= max => Right(Bounds(min, max))
          case _ => Left("Unable to bind bounds")
        }
      }
    }

    def unbind(key: String, bounds: Bounds) =
      key+".min=" + intBinder.unbind(bounds.min) + "&" + key+".max=" + intBinder.unbind(bounds.max)
  }
}

object Main extends Controller {
  def list(price: Bounds) = {
    …
  }
}

To be used with the following routes file:

GET  /list       controllers.Main.list(p: Bounds)

The following requests would match:

/list?p.min=0&p.max=42

@blast-hardcheese
Copy link

This is slick, thanks

@scottkwalker
Copy link

In the Build.scala I was having a problem with the compile not finding routesImport. In Play 2.4 it was moved, so now you need to add import play.sbt.routes.RoutesKeys.routesImport to the Build file

@samgj18
Copy link

samgj18 commented Mar 1, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment