Skip to content

Commit

Permalink
Implemented support for routing code samples
Browse files Browse the repository at this point in the history
  • Loading branch information
jroper committed Apr 12, 2013
1 parent b3ce8d7 commit d07512d
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 168 deletions.
71 changes: 15 additions & 56 deletions documentation/manual/scalaGuide/main/http/ScalaRouting.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,13 @@ Routes are defined in the `conf/routes` file, which is compiled. This means that

Let’s see what a route definition looks like:

```
GET /clients/:id controllers.Clients.show(id: Long)
```
@[clients-show](code/scalaguide.http.routing.routes)

Each route starts with the HTTP method, followed by the URI pattern. The last element is the call definition.

You can also add comments to the route file, with the `#` character.

```
# Display a client.
GET /clients/:id controllers.Clients.show(id: Long)
```
@[clients-show-comment](code/scalaguide.http.routing.routes)

## The HTTP method

Expand All @@ -44,17 +39,13 @@ The URI pattern defines the route’s request path. Parts of the request path ca

For example, to exactly match incoming `GET /clients/all` requests, you can define this route:

```
GET /clients/all controllers.Clients.list()
```
@[static-path](code/scalaguide.http.routing.routes)

### Dynamic parts

If you want to define a route that retrieves a client by ID, you’ll need to add a dynamic part:

```
GET /clients/:id controllers.Clients.show(id: Long)
```
@[clients-show](code/scalaguide.http.routing.routes)

> Note that a URI pattern may have more than one dynamic part.
Expand All @@ -64,95 +55,63 @@ The default matching strategy for a dynamic part is defined by the regular expre

If you want a dynamic part to capture more than one URI path segment, separated by forward slashes, you can define a dynamic part using the `*id` syntax, which uses the `.*` regular expression:

```
GET /files/*name controllers.Application.download(name)
```
@[spanning-path](code/scalaguide.http.routing.routes)

Here for a request like `GET /files/images/logo.png`, the `name` dynamic part will capture the `images/logo.png` value.

### Dynamic parts with custom regular expressions

You can also define your own regular expression for the dynamic part, using the `$id<regex>` syntax:

```
GET /clients/$id<[0-9]+> controllers.Clients.show(id: Long)
```
@[regex-path](code/scalaguide.http.routing.routes)

## Call to the Action generator method

The last part of a route definition is the call. This part must define a valid call to a method returning a `play.api.mvc.Action` value, which will typically be a controller action method.

If the method does not define any parameters, just give the fully-qualified method name:

```
GET / controllers.Application.homePage()
```
@[home-page](code/scalaguide.http.routing.routes)

If the action method defines some parameters, all these parameter values will be searched for in the request URI, either extracted from the URI path itself, or from the query string.

```
# Extract the page parameter from the path.
GET /:page controllers.Application.show(page)
```
@[page](code/scalaguide.http.routing.routes)

Or:

```
# Extract the page parameter from the query string.
GET / controllers.Application.show(page)
```
@[page](code/scalaguide.http.routing.query.routes)

Here is the corresponding, `show` method definition in the `controllers.Application` controller:

```scala
def show(page: String) = Action {
loadContentFromDatabase(page).map { htmlContent =>
Ok(htmlContent).as("text/html")
}.getOrElse(NotFound)
}
```
@[show-page-action](code/ScalaRouting.scala)

### Parameter types

For parameters of type `String`, typing the parameter is optional. If you want Play to transform the incoming parameter into a specific Scala type, you can explicitly type the parameter:

```
GET /client/:id controllers.Clients.show(id: Long)
```
@[clients-show](code/scalaguide.http.routing.routes)

And do the same on the corresponding `show` method definition in the `controllers.Clients` controller:

```scala
def show(id: Long) = Action {
Client.findById(id).map { client =>
Ok(views.html.Clients.display(client))
}.getOrElse(NotFound)
}
```
@[show-client-action](code/ScalaRouting.scala)

### Parameters with fixed values

Sometimes you’ll want to use a fixed value for a parameter:

```
# Extract the page parameter from the path, or fix the value for /
GET / controllers.Application.show(page = "home")
GET /:page controllers.Application.show(page)
```
@[page](code/scalaguide.http.routing.fixed.routes)

### Parameters with default values

You can also provide a default value that will be used if no value is found in the incoming request:

```
# Pagination links, like /clients?page=3
GET /clients controllers.Clients.list(page: Int ?= 1)
```
@[clients](code/scalaguide.http.routing.defaultvalue.routes)

### Optional parameters

You can also specify an optional parameter that does not need to be present in all requests:

@[optional](code/scalaguide.http.routing.routes)
```
# The version parameter is optional. E.g. /api/list-all?version=3.0
GET /api/list-all controllers.Api.list(version: Option[String])
Expand Down
111 changes: 111 additions & 0 deletions documentation/manual/scalaGuide/main/http/code/ScalaRouting.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package scalaguide.http.routing

import org.specs2.mutable.Specification
import play.api.test.FakeRequest
import play.api.mvc._
import play.api.test.Helpers._
import play.core.Router

package controllers {

object Client {
def findById(id: Long) = Some("showing client " + id)
}

object Clients extends Controller {

// #show-client-action
def show(id: Long) = Action {
Client.findById(id).map { client =>
Ok(views.html.Clients.display(client))
}.getOrElse(NotFound)
}
// #show-client-action

def list() = Action(Ok("all clients"))
}

object Application extends Controller {
def download(name: String) = Action(Ok("download " + name))
def homePage() = Action(Ok("home page"))

def loadContentFromDatabase(page: String) = Some("showing page " + page)

// #show-page-action
def show(page: String) = Action {
loadContentFromDatabase(page).map { htmlContent =>
Ok(htmlContent).as("text/html")
}.getOrElse(NotFound)
}
// #show-page-action
}

object Items extends Controller {
def show(id: Long) = Action(Ok("showing item " + id))
}

object Api extends Controller {
def list(version: Option[String]) = Action(Ok("version " + version))
}
}

package query {
package object controllers {
val Application = scalaguide.http.routing.controllers.Application
}
}

package fixed {
package object controllers {
val Application = scalaguide.http.routing.controllers.Application
}
}

package defaultvalue.controllers {
object Clients extends Controller {
def list(page: Int) = Action(Ok("clients page " + page))
}
}

object ScalaRoutingSpec extends Specification {
"the scala router" should {
"support simple routing with a long parameter" in {
contentOf(FakeRequest("GET", "/clients/10")).trim must_== "showing client 10"
}
"support a static path" in {
contentOf(FakeRequest("GET", "/clients/all")) must_== "all clients"
}
"support a path part that spans multiple segments" in {
contentOf(FakeRequest("GET", "/files/foo/bar")) must_== "download foo/bar"
}
"support regex path parts" in {
contentOf(FakeRequest("GET", "/items/20")) must_== "showing item 20"
}
"support parameterless actions" in {
contentOf(FakeRequest("GET", "/")) must_== "home page"
}
"support passing parameters from the path" in {
contentOf(FakeRequest("GET", "/foo")) must_== "showing page foo"
}
"support passing parameters from the query string" in {
contentOf(FakeRequest("GET", "/?page=foo"), query.Routes) must_== "showing page foo"
}
"support fixed values for parameters" in {
contentOf(FakeRequest("GET", "/foo"), fixed.Routes) must_== "showing page foo"
contentOf(FakeRequest("GET", "/"), fixed.Routes) must_== "showing page home"
}
"support default values for parameters" in {
contentOf(FakeRequest("GET", "/clients"), defaultvalue.Routes) must_== "clients page 1"
contentOf(FakeRequest("GET", "/clients?page=2"), defaultvalue.Routes) must_== "clients page 2"
}
"support optional values for parameters" in {
contentOf(FakeRequest("GET", "/api/list-all")) must_== "version None"
contentOf(FakeRequest("GET", "/api/list-all?version=3.0")) must_== "version Some(3.0)"
}

}

def contentOf(rh: RequestHeader, router: Router.Routes = Routes) = contentAsString(router.routes(rh) match {
case e: EssentialAction => AsyncResult(e(rh).run)
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# #clients
# Pagination links, like /clients?page=3
GET /clients controllers.Clients.list(page: Int ?= 1)
# #clients
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# #page
# Extract the page parameter from the path, or fix the value for /
GET / controllers.Application.show(page = "home")
GET /:page controllers.Application.show(page)
# #page
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# #page
# Extract the page parameter from the query string.
GET / controllers.Application.show(page)
# #page
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

# #static-path
GET /clients/all controllers.Clients.list()
# #static-path

# #clients-show-comment
# Display a client.
# #clients-show ###skip
GET /clients/:id controllers.Clients.show(id: Long)
# #clients-show #clients-show-comment

# #spanning-path
GET /files/*name controllers.Application.download(name)
# #spanning-path

# #regex-path
GET /items/$id<[0-9]+> controllers.Items.show(id: Long)
# #regex-path

# #home-page
GET / controllers.Application.homePage()
# #home-page

# #page
# Extract the page parameter from the path.
GET /:page controllers.Application.show(page)
# #page

# #optional
# The version parameter is optional. E.g. /api/list-all?version=3.0
GET /api/list-all controllers.Api.list(version: Option[String])
# #optional
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@(msg: String)
@msg
12 changes: 11 additions & 1 deletion documentation/project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PlaySourceGenerators._

object ApplicationBuild extends Build {

val main = Project("Play-Documentation", file(".")).settings(
lazy val main = Project("Play-Documentation", file(".")).settings(
version := PlayVersion.current,
scalaVersion := PlayVersion.scalaVersion,
libraryDependencies ++= Seq(
Expand All @@ -28,6 +28,14 @@ object ApplicationBuild extends Build {
ds.flatMap(d => ScalaTemplates(s, d, g, t, defaultTemplatesImport ++ defaultScalaTemplatesImport))
},

sourceGenerators in Test <+= (state, javaManualSourceDirectories, sourceManaged in Test) map { (s, ds, g) =>
ds.flatMap(d => RouteFiles(s, d, g, Seq("play.libs.F"), false))
},
sourceGenerators in Test <+= (state, scalaManualSourceDirectories, sourceManaged in Test) map { (s, ds, g) =>
ds.flatMap(d => RouteFiles(s, d, g, Seq(), false))
},


templatesTypes := {
case "html" => ("play.api.templates.Html", "play.api.templates.HtmlFormat")
case "txt" => ("play.api.templates.Txt", "play.api.templates.TxtFormat")
Expand All @@ -39,4 +47,6 @@ object ApplicationBuild extends Build {
lazy val javaManualSourceDirectories = SettingKey[Seq[File]]("java-manual-source-directories")
lazy val scalaManualSourceDirectories = SettingKey[Seq[File]]("scala-manual-source-directories")

// We can't use the Play SBT routes compiler function, since we need to do some special stuff with imports

}
Loading

0 comments on commit d07512d

Please sign in to comment.