seratch's weblog in Japanese

About Scala, Java and Ruby programming in Japaense. If you need English information, go to http://blog.seratch.net/

Play ドキュメントを Skinny で書くと - Actions, Controllers and Results

これは Play framework 2.x Scala Advent Calendar 2013 の 10 日目です。

http://www.adventar.org/calendars/114

ご存知の方もいらっしゃるかと思いますが、私は Skinny Framework というのをつくっています。これは Servlet ベースのフルスタックな Web アプリ開発フレームワークで Web アプリ部分については「Scalatra を便利にする」というスタンスで機能拡張しています。

http://skinny-framework.org/

今回は Play Framework のドキュメントの内容を Skinny の場合だとどう書くかを説明しながら両者の比較をしてみたいと思います。なお Skinny Framework のバージョンは 0.9.20 です。Skinny はまだ 1.0 がリリースされていないフレームワークなので(1.0 は 2014/3 までにリリース予定)、ここでサポートしていない機能が今後追加されたり、また場合によっては API が変更になる場合があります。

http://www.playframework.com/documentation/2.2.x/ScalaHome

なお、明日も明後日もこのアドベントカレンダーはずっと空いているので、もしも誰も入ってこないとなると、毎日こんな感じになりますからね。覚悟してください。

Actions, Controllers and Results

http://www.playframework.com/documentation/2.2.x/ScalaActions

What is an Action?

val echo = Action { request =>
  Ok("Got request [" + request + "]")
}

これを Skinny で書くとこのようになります。Action がないだけですね。

def echo = Ok("Got request [" + request + "]")

これは Scalatra の ActionResult を使っています。ドキュメントはこちら。

https://github.com/scalatra/scalatra/blob/2.2.x_2.10/core/src/main/scala/org/scalatra/ActionResult.scala

このケースだと ActionResult を省略して

def echo = "Got request [" + request + "]"

とだけ書いても OK です。

Building an Action

Platy の Action とは違って Scalatra の ActionResult は

case class ActionResult(
  status: ResponseStatus, 
  body: Any, 
  headers: Map[String, String])

という構造なので特に難しいことはないと思います。これを生成する factory として Ok とか NotFound とかがあるだけです。

Controllers are action generators

Play では Controller のコードはこんな感じになりますが

package controllers
import play.api.mvc._
object Application extends Controller {
  def index = Action {
    Ok("It works!")
  }
}

同じ内容が Skinny ではこうなります。単に文字列を返すと 200 OK で指定された文字列を body として応答します。Skinny では object ではなく class になっていますが、これはルーティングのところで説明することになります。

package controller
import skinny._
class Application extends SkinnyController {
  def index = "It works!"
}

Play ではこのようにパラメータをメソッド引数として取得できますが

def hello(name: String) = Action {
  Ok("Hello " + name)
}

0.9.20 時点で Skinny では同じことはできません。params から取得します。

def hello = "Hello " + params.getAs[String]("name").getOrElse("Anonymous")

Simple results

def index = Action {
  SimpleResult(
    header = ResponseHeader(200, Map(CONTENT_TYPE -> "text/plain")),
    body = Enumerator("Hello world!".getBytes())
  )
}

これは Skinny では

def index = {
  status = 200
  contentType = "text/plain"
  "Hello world!"
}

となります。

val ok = Ok("Hello world!")
val notFound = NotFound
val pageNotFound = NotFound(<h1>Page not found</h1>)
val badRequest = BadRequest(views.html.form(formWithErrors))
val oops = InternalServerError("Oops")
val anyStatus = Status(488)("Strange response type")

はそれぞれ、ほぼ同じように書くなら以下のようになります。

// val ok = Ok("Hello world!")
val ok = Ok("Hello world!")

// val notFound = NotFound
val notFound = NotFound()

// val pageNotFound = NotFound(<h1>Page not found</h1>)
val pageNotFound = NotFound(<h1>Page not found</h1>)

// val badRequest = BadRequest(views.html.form(formWithErrors))
set("formWithErrors" -> formWithErrors)
status = 400
render("/form")

// val oops = InternalServerError("Oops")
val oops = InternalServerError("Oops")

// val anyStatus = Status(488)("Strange response type")
status = 488
"Strange response type"

Redirects are simple results too

ダイレクトを意味する API のデフォルトが Play では 303 ですが Scalatra では 302 という違いがあります*1

// 303 redirect
def index = Action {
  Redirect("/user/home")
}
// 301 redirect
def index = Action {
  Redirect("/user/home", MOVED_PERMANENTLY)
}

これを Skinny でやると以下のようになります。ScalatraBase にある redirect メソッドは 302 でリダイレクトします。Scalatra は redirect と ActionResult を提供していて Skinny が redirect301 のようなメソッド 3 つを提供しています。

https://github.com/skinny-framework/skinny-framework/blob/develop/framework/src/main/scala/skinny/controller/feature/ExplicitRedirectFeature.scala

// 301 redirect
def index = redirect301("/user/home")
def index = MovedPermanently("/user/home")
// 302 redirect
def index = redirect("/user/home")
def index = redirect302("/user/home")
def index = Found("/user/home")
// 303 redirect
def index = redirect303("/user/home")
def index = SeeOther("/user/home")

“TODO” dummy page

def index(name:String) = TODO

これは存在しないですが

def index = ???

とでもしておけばいいのではないでしょうか。

明日は?

以上、「Actions, Controllers and Results」のページでした。Scalatra をご存知の方はお分かりかと思いますが、半分以上は Scalatra の機能です。Skinny は Scalatra をより便利にするというスタンスなのでこのような形になります。

明日も担当者が現れなかったら・・・続きをやります。

Advent Calendar で公開しなかった場合も続きは普通の記事として公開しますので、割り込みをお待ちしております。

http://www.adventar.org/calendars/114

*1:指定がないときは 302 が妥当な気がしますが