URL mapping

Custom URL Mapping

Since Grails 0.5 it is possible to map arbitrary URLs to controllers and actions. This is done via the grails-app/conf/*UrlMappings.groovy file, which uses an internal DSL for defining mappings between URLs and controllers:

class MyUrlMappings {
  static mappings = {
    "/product/$id" {
       controller = "product"
       action = "show"
    }

"/$blog/$year/$month/$id" { controller = "blog" action = "show" constraints { year(matches:/d{4}/) month(matches:/d{2}/) } } } }

The first example will match all URLs under /product to a ProductController with an action show . The id parameter will get the value of the second part of the URL:
class ProductController {
     def show = { render params.id }
}
The above action will render the text "MacBook" for the URL "/product/MacBook".

Please note that the domain-class name when specifying the controller starts with a lower-case character.

The second example is more complex and breaks the URL into parts that map to request parameters. It also constraints the "year" and "month" parts of the URL using Grails' standard constraints mechanism. This would allow you to do things like:

/graemerocher/2007/01/my_funky_blog_entry

And it would map the show action of the BlogController with the params object containing

[blog:"graemerocher", year:"2007", month:"01", id:"my_funky_blog_entry"]

As of Grails 0.5.5 an alternate syntax is supported:

class MyUrlMappings {
  static mappings = {
    "/product/$id" (controller:"product", action:"show")

"/$blog/$year/$month/$id" (controller:"blog", action:"show"){ constraints { year(matches:/d{4}/) month(matches:/d{2}/) } } } }

Optional URL Mapping

Some parts of a URL can be declared optional by placing a question mark directly after the variable name, which declares them as being optional:

static mappings = {
     "/$blog/$year/$month?/$id?" {
          controller = "blog"
          action = "show"
          constraints {
               year(matches:/d{4}/)
               month(matches:/d{2}/)
          }
     }
}
In this case the mapping will match all of the following URLs:
/myblog/2007
/myblog/2007/02
/myblog/2007/02/a_blog_entry

Precedence Rules

URL mappings use a precedence rule that states the "strongest" URL will match first. To explain this in detail consider the following two examples:

static mappings = {
    "/product/$id" {
       controller = "product"
       action = "show"
    }

"/$blog/$year?/$month?/$id?" { controller = "blog" action = "show" constraints { year(matches:/d{4}/) month(matches:/d{2}/) } } }

The URL /product/1970 could map to either the first mapping or the second (a product with an id of 1970 or a blog with the name "product" and a year of 1970. However because the product using a concrete definition which is not calculated at runtime it takes precedence over the second entry.

The precedence is calculated on a token by token basis. A URL is made up of a number of tokens, divided by /, the more tokens a URL has the higher its precedence. The more concrete definitions within the tokens the higher its precedence.

Re-writing Links from Mappings

When you use the Grails tags such as <g:link> and <g:form> they will automatically re-write the links based on the mappings. So for example for the mappings above they would produce:

<g:link controller="blog" action="show" params="[blog:'fred', year:2007]">My Blog</g:link>
Becomes:
<a href="/app/fred/2007">My Blog</a>
The same applies to all other tags that do linking.

Mapping to views

Not yet implemented!
It should also be possible to map to a standalone view that doesn't have a controller or action:
static mappings = {
    "/product/$id"(view:"product/show")
}

RESTful URL Mappings

Grails 0.6 supports REST style URL mappings where you can map different HTTP methods to different actions in a controller:

static mappings = {
   "/product" {
     controller = "product"
     action = [POST:"create"]
     // note that requests from other methods
     // should be sent to the 'default' action
   }
   "/product/$id"{
     controller = "product"
     action = [POST:"create", GET:"retrieve", PUT:"update", DELETE:"delete"]
   }
}
The RESTful ProductController for the above mapping will contain 4 actions as follows:
import grails.converters.*

class ProductController { def create = { def product = new Product() product.fromXml(request.inputStream) if(product.save()) response.redirect(status:201, url:"product/${product.id}") } def retrieve = { def product = Product.get(params.id) if(product) render(product as XML) } def update = { def product = Product.get(params.id) if(product) { product.fromXml(request.inputStream) if(product.save()) response.send(status:200, message:"${product.id} saved") else response.sendError(...) } } def delete = { def product = Product.get(params.id) if(product) product.delete() response.send(status:200, message:"${product.id} deleted") } }

No Comments Yet

Post a Comment