Lambdaカクテル

京都在住Webエンジニアの日記です

scalatra + twirlでリバースルーティングする

暮らしているとリバースルーティングしたい日があります.例えばテンプレートの中で別のエンドポイントへのリンクを作りたいとき,直にURLを書きたくないですよね.修正に弱いし. いまどきのWebアプリケーションはエンドポイントを渡すと勝手にURLが作られるようになっているはずです.

そこでリバースルーティングによってme.url(me.someEndpoint)みたいにするとURLがもらえる仕組みをscalatra + twirlで作ります.

TL;DR

  • サーブレットにUrlGeneratorSupportをmix-inする
  • テンプレートをレンダするとき,twirlにimplicit valで呼び出し元のサーブレットと,暗黙に生えているjavax.servlet.http.HttpServletRequestとを渡す
  • テンプレート側でもこれをimplicitに受け取る

環境

val ScalatraVersion = "2.7.0"
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.4.2")

リバースルーティング

リバースルーティングする方法はマニュアルに書いてあります.

scalatra.org

要約すると,エンドポイントを名前付きで宣言しておき,url(エンドポイント)とするとURLがもらえるとのことです.マニュアルのコードを引用します.

class MyApp extends ScalatraServlet with UrlGeneratorSupport {
  // When you create a route, you can save it as a variable
  val viewUser = get("/user/:id") {
     // your user action would go here
   }

  post("/user/new") {
    // url method provided by UrlGeneratorSupport.  Pass it the route
    // and the params.
    redirect(url(viewUser, "id" -> newUser.id))
  }
}

これをtwirlでやるために工夫します.マニュアルに登場するurlメソッドはUrlGeneratorSupportをmix-inしたサーブレットに生えるので,直接テンプレートから呼び出せません.そこでサーブレット自体をテンプレートに渡します.マニュアルのコードを改変して説明します.

class MyApp extends ScalatraServlet with UrlGeneratorSupport {
  implicit val servlet = this // implicit化する
  val viewUser = get("/user/:id") {
      views.html.foo()
   }
}

implicitを使ってサーブレット自体がテンプレートに渡るようにしました.Twirl側でもこれを受け取れるようにします.

// views.html.foo
@()(impilcit servlet: MyApp)

@servlet.url(servlet.viewUser)

するとtwirlテンプレートでurlが呼び出せるように……なりません.

could not find implicit value for parameter req: javax.servlet.http.HttpServletRequest

urlメソッドはどうやらリクエストオブジェクトを暗黙に要求するようです.これもimplicitを使います.とはいえ元々からreqimplicitなので,テンプレートで要求するだけでよいです.

@()(impilcit servlet: MyApp, req: javax.servlet.http.HttpServletRequest)

@servlet.url(servlet.viewUser)

型表記が長ったらしくなったので,twirlテンプレートでは自動的にjavax.servlet.http.HttpServletRequestがimportされるようにします.これはsbtの設定で可能です.

// build.sbt
TwirlKeys.templateImports += "javax.servlet.http.HttpServletRequest"

するとこう書けます.

@()(impilcit servlet: MyApp, req: HttpServletRequest)

@servlet.url(servlet.viewUser)

めでたし

まとめ

リバースルーティングを若干ムリヤリ達成できました.ところでscalatraチームはテンプレートエンジンとしてscalateをおすすめしています.

scalatra.org

こっちを使う場合は特に何もせずにurlが呼べるようです.こっちで良さそう.よかったですね.

追記

:いろいろある:

貴様!この記事の筆者をフォローしてください!(しなさい)