play frameworkでPUT, DELETE, PATCH等のリクエストを<form>から受け取る
RESTfulなパスにしたいとき、<form>タグでmethod=PUT, DELETE等を送れない問題*1にぶち当たることがある。
全部POSTで送ればいいのだが、どうしてもPUTやDELETEを使いたいRESTful脳な人のための解決方法。
解決方法
Railsはこの願いを叶えるために_methodというパラメータを渡した場合はそのメソッドとしてリクエストを解釈するという仕様がある。(formヘルパーは、勝手に<input type="hidden" name="_method" value="put">というhiddenパラメータをつけてくれる *2 )play frameworkでもこれとほぼ同様の手段で解決できるよう、@helper.formの代わりになるヘルパーと、HTTPリクエストの処理の仕方を変えるようにした。
app/views/helper/formExtended.scala.html
GET or POSTでない場合のみGETパラメータに"_method=METHOD"をつけるヘルパー。@helper.formの代わりに@helper.formExtendedとしてあげれば良い。
@(action: Call, args: (Symbol, String)*)(body: => Html) @availableFormMethod(method: String) = @{ method.toUpperCase match { case "GET" | "POST" | "" => true case _ => false } } @appendMethod(action: Call) = @{ val (url, method) = (action.url, action.method) if(availableFormMethod(method)) { url } else { url + (if(url.contains('?')) "&" else "?") + "_method=" + method } } <form action="@appendMethod(action)" method="@if(availableFormMethod(action.method)) {@action.method} else {POST}" @toHtmlArgs(args.toMap)> @body </form>
(大抵のケースだとCSRF対策でtokenを送っていると思うので、送るロジックをついでにここに書いちゃえばいい)
app/Global.scala
あとはGlobal.scalaでリクエストのメソッドを書き換えてあげれば完成
object Global extends GlobalSettings with Results { .... override def onRouteRequest(request: RequestHeader): Option[Handler] = { val requestRewrited = request.method.toUpperCase match { case "POST" => request.copy(method = request.getQueryString("_method").getOrElse("POST")) case _ => request } super.onRouteRequest(requestRewrited) } }
(追記) @formExtended というヘルパーを作るより、単純にCallを書き換えてくれるやつ作ったほうが良いかも