Play2.0でURLに一律でパラメータを付ける

"Play 2.0"もとい"Playframewok 2.0"もとい"Play framework 2.0"です。検索しにくいんだよksg。
使ったバージョンは2.0.3です。
あまり良いやり方じゃない気がするので、良いやり方があれば教えて下さい。

目的

Play 2.0でHTMLテンプレート上でアクションを呼び出すリンクを生成する際に、リバースルーティングを使ってとてもDRYで書けて良いと思う。*1
下はサンプルのcomputer-databaseの例。
ルーティング設定ファイル(conf/routes):

...
# Edit existing computer
GET     /computers/:id              controllers.Application.edit(id:Long)
POST    /computers/:id              controllers.Application.update(id:Long)
...

HTMLテンプレート(app/views/list.scala.html):

...
                    <td><a href="@routes.Application.edit(computer.id.get)">@computer.name</a></td>
...

HTMLテンプレート上で@routes.Application.edit(computer.id.get)のように関数呼び出しの形で書くと、routesファイルの定義に従ってURLを生成してくれる。URLを変更したいときは、routeファイルだけ直せばHTMLテンプレートは直すが必要ない。とてもDRYでよい。


ここで、URLに一律で特定のパラメータ(例えばguid=ONなど)を付けたいなあと思ったんだけど、スマートなやり方が思い浮かばなかった。
ダーティーなやり方が以下になります。

一律でパラメータを付加する(ダーティーな方法)

まず、URLの生成に使用されるクラスであるplay.api.mvc.Callを偽装するオブジェクトを定義する。パッケージ名は何でも良いけど、名前は「Call」である必要がある。
app/api/mvc/Call.scala:

package api.mvc

import play.core.parsers.FormUrlEncodedParser

object Call {
   def apply(method: String, url: String) = {
      val params = FormUrlEncodedParser.parse(url.split('?').drop(1).mkString("?"));
      if (params.isDefinedAt("guid")) // 追加済み
         new play.api.mvc.Call(method, url)
      else
          if (params.isEmpty)
             new play.api.mvc.Call(method, url + "?guid=ON") 
          else
             new play.api.mvc.Call(method, url + "&guid=ON")
     }
  }

次に、プロジェクトファイルで、ルーティングファイルのコンパイル(ソースコード生成)時に追加するimport先に、上記のオブジェクトを読み込む設定を追加する。
project/Build.scala

...
    val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
       // Add your own project settings here
       routesImport ++= Seq("api.mvc.Call") // この行を追加
    )
...

ここでconf/routesファイルを更新しておく。
playでプロジェクトファイルを再読み込み or 再起動

[computer-database] $ reload
[info] Loading project definition from C:\Projects\repos\Play20\samples\scala\computer-database\project
[info] Set current project to computer-database (in build file:/C:/Projects/repos/Play20/samples/scala/computer-database/)
[computer-database] $ run

--- (Running the application from SBT, auto-reloading is enabled) ---

[info] play - Listening for HTTP on port 9000...

(Server started, use Ctrl+D to stop and go back to the console...)

トップページを見てみると...例えば「Computer name」と「ACE」のリンクのURLが

Before
/computers?s=-2
/computers/381
After
/computers?s=-2&guid=ON
/computers/381?guid=ON

ちゃんとついている。

解説

リバースルーティングは、routeファイルを元にリバースルーティング用のクラスを自動生成して実現してるみたい。
「target/scala-2.9.1/src_managed/」の下にソースコードが生成されている。
target/scala-2.9.1/src_managed/main/controllers/routes.java:

// @SOURCE:C:/Projects/repos/Play20/samples/scala/computer-database/conf/routes
// @HASH:d3cc52ad69d3a86d9c774c3c45e645163f27e021
// @DATE:Sun Sep 23 21:32:28 JST 2012

package controllers;

public class routes {
public static final controllers.ReverseApplication Application = new controllers.ReverseApplication();
public static final controllers.ReverseAssets Assets = new controllers.ReverseAssets();
public static class javascript {
public static final controllers.javascript.ReverseApplication Application = new controllers.javascript.ReverseApplication();
public static final controllers.javascript.ReverseAssets Assets = new controllers.javascript.ReverseAssets();    
}   
public static class ref {
public static final controllers.ref.ReverseApplication Application = new controllers.ref.ReverseApplication();
public static final controllers.ref.ReverseAssets Assets = new controllers.ref.ReverseAssets();    
} 
}
                            

target/scala-2.9.1/src_managed/main/routes_reverseRouting.scala:

// @SOURCE:C:/Projects/repos/Play20/samples/scala/computer-database/conf/routes
// @HASH:d3cc52ad69d3a86d9c774c3c45e645163f27e021
// @DATE:Sun Sep 23 21:32:28 JST 2012

import play.core._
import play.core.Router._
import play.core.j._

import play.api.mvc._

import Router.queryString
...
package controllers {
...
class ReverseApplication {
...
// @LINE:16
def edit(id:Long) = {
   Call("GET", "/computers/" + implicitly[PathBindable[Long]].unbind("id", id))
}

アクションやテンプレートからroute.Application.メソッド名(パラメータ)と呼び出すと、routes.javaで定義されたApplicationというstaticなフィールド(中身はroutes_reverseRouting.scalaで定義されたReverseApplicationというクラス)のメソッドが呼び出され、urlがセットされたCallというケースクラスが生成されることでURLの文字列を生成している。
設定のroutesImportを追加してやることで、Callの呼び出しを挿げ替えるのが今回のハックの内容になる。

import play.api.mvc._
import api.mvc.Call    // ←この行が追加される
...
// @LINE:16
def edit(id:Long) = {
   // ↓の呼び出し先が、play.api.mvc.Callからapi.mvc.Callに変わる
   Call("GET", "/computers/" + implicitly[PathBindable[Long]].unbind("id", id))
}

あとは、api.mvc.CallでURLを操作して元のplay.api.mvc.Callを生成して戻してやることで、無事にパラメータを追加できる。

逆にルーティングの際はパラメータは適当に無視してくれるみたいで、ちゃんとアクションが呼ばれます。

*1:元ネタはrailsかな?