家でWebアプリを組む時、ここ数年はRailsを使っていた。
しかしスコラ派を自称する私としては、そろそろPlayに流れるべきではないかと思ってみた。
そこで今回はPlayを導入して、数値をpostしてサーバで足し算して返すだけの簡易なサンプルアプリを作るまでの道のりをまとめてみた。DBとかの操作はまた今度。
Play2.0.4。OSはLinuxを想定。
とりあえず公式サイトからzipファイルをダウンロードして、適当な場所に解凍する。
中に入っているplayコマンドを叩いて、example(仮)という名前のプロジェクトを作ってみる。
$ ./play new example
上記コマンドを実行すると、「Scalaにする? それともJavaにする?」と新妻のような質問をしてくるので、1(Scala)と答える。
Which template do you want to use for this new application? 1 - Create a simple Scala application 2 - Create a simple Java application 3 - Create an empty project > 1
これでexampleという名前のプロジェクトが生成された。
$ cd example
生成されたプロジェクトの中身はどうなっているのか。
こんな感じらしい。
$ tree |-- README |-- app | |-- controllers | | `-- Application.scala | `-- views | |-- index.scala.html | `-- main.scala.html |-- conf | |-- application.conf | `-- routes |-- project | |-- Build.scala | |-- build.properties | `-- plugins.sbt `-- public |-- images | `-- favicon.png |-- javascripts | `-- jquery-1.7.1.min.js `-- stylesheets `-- main.css
Railsに馴染んでいる人には分かりやすい構成。
appにcontrollerとviewがいて(modelもここに置く)、confに設定を書いて、publicに公開ファイルを置く。
PlayはServletを使ってないので、Javaプログラマにとってはおなじみのweb.xmlはいない。設定はproject配下でSBTを使って記述している。
生成したプロジェクトの中をいじる前にちょっと寄り道をしておく。
落としてきたzipファイルを解凍した中にあるsamplesディレクトリの中に、いろいろサンプルが入っている。これを少し動かして、感触を掴んでみる。
$ cd ../samples/scala/ $ ls comet-clock comet-live-monitoring computer-database forms helloworld websocket-chat zentasks
samplesの中には上記のようにいくつかのプロジェクトが置いてある。
cometさんとかwebsocketさんとかにも惹かれるけど、最初に見るのはもちろんhelloworld。アカシックレコードにもそう書いてあります。
$ cd helloworld
ここでサーバを立ち上げて、helloworldプロジェクトを実行してみる。play runで、カレントディレクトリのプロジェクトを実行できる。
$ ../../../play run
playコマンドのある場所にはpathを通しておいた方が良いと思う。私はフルパス派の家柄に生まれたので入れないけど。
上記のコマンドを打ってしばらく待つと、サーバが立ち上がったメッセージが表示される。
(Server started, use Ctrl+D to stop and go back to the console...)
9000番ポートで立ち上がったらしいので、下記のURLにリクエストしてみる。
http://localhost:9000/
表示されたページがhelloworldの実行結果。
この子のapp/controllers、app/views/、app/conf/routesあたりを見て回ると、なんとなくplayさんのことが分かった気になる。
一般的なデータベース操作に関するソースを見たい場合は、computer-databaseが参考になる。conf/application.confにデータベースの設定が載っていたり、app/modelsにSQLを実行するコードがいたり。create文はconf/evolutionsにいる。
さて、話戻してさっき作ったexampleプロジェクト。
まずはControllerは何もせずにviewに置いたHTMLを表示するだけの機能を書いてみる。
とりあえずexampleディレクトリに戻る。
$ cd ../../../example
app/controllers/Foo.scalaというファイルを作って、以下のような内容を記述する。
package controllers
import play.api._
import play.api.mvc._
object Foo extends Controller {
def index = Action {
Ok(views.html.foo())
}
}
上記の記述は、indexにアクセスされたら、Ok(200)でviewのfooの内容を返す、というもの。
Okはplay.api.mvc.Controllerで定義されている。Controllerには404を返すNotFoundとか、201を返すCreatedとか、たいていのレスポンスコードを返す用の子が用意されている。さすがに418 I'm a teapotはいないけど。
次にViewの用意。controllerでviews.html.fooと定義したので、それに対応するようにapp/views/foo.scala.htmlというファイルを作り、HTMLを記述する。
<!DOCTYPE html>
<html>
<head>
<title>title</title>
</head>
<body>
foo
</body>
</html>
見ての通り、fooと表示されるだけのHTML。
最後に、今回作ったControllerにアクセスができるように、conf/routesに以下のような記述を追加する
GET /foo controllers.Foo.index
GETで/fooを呼び出されたら、controller.Fooのindexに処理を渡すよ、という意味。
これでサーバを立ち上げてみる。
$ ../play run
http://localhost/fooにリクエストすると、fooとだけ表示されるページが表示されるはず。
とりあえずこんな感じで、ControllerとViewを書いてroutesに記述すれば、HTMLの表示はできるようだ。
HTMLを表示するだけでは何の役にも立たないので、次にControllerから変数の値を渡してみる。
前項で作ったapp/controllers/Foo.scalaを下記のように書き換える。
object Foo extends Controller {
def index = Action {
Ok(views.html.foo("へんすー"))
}
}
これでfooに対して「へんすー」という値が渡された。
次はapp/views/foo.scala.htmlで渡された値を受け取る。
@(hensu: String)
<!DOCTYPE html>
<html>
<head>
<title>title</title>
</head>
<body>
@hensu
</body>
</html>
1行目の@(hensu: String)という記述で、渡された値をhensuという変数名で受け取っている。
あとはこの変数を表示したい場所で、@hensuと書けば、変数の中身が画面に表示される。
http://localhost:9000/fooにリクエストして結果を確認してみる。
ちゃんと変数の値が表示された。play runで走らせている場合、サーバの再起動は必要ない。自動でコンパイルが走る。
下記のように複数の変数を渡すことも可能。
@(hensu1: String, hensu2: Int, hensu3: Double = 0.0)
PlayのテンプレートはScalaベースで作られている。上の引数の表記も、Scalaユーザには見慣れた書き方になっている。
また、@を付けて括弧でくくれば、概ねScalaのコードを実行できる。
九九の表示とか。
@for( i <- 1 to 9; j <- 1 to 9 ) {
@(i * j)
@Html( if( j == 9 ) "<br>" else "" )
}
上の例では、普通にブロック内でbrタグを書こうとすると<や>にエスケープされてしまうので、@Htmlでタグは出力している。
viewから他のviewを呼び出すこともできる。最初にplay newした際に自動作成されたapp/views/index.scala.htmlでは、別のview、main.scala.htmlを呼び出している。
試しにfoo.scala.htmlから、main.scala.htmlを呼び出してみる。
main.scala.htmlは以下のように、titleとcontentという2つの引数を取っている。
@(title: String)(content: Html)
contentの型はplay.api.templates.Htmlになっている。@main(タイトル) { ブロック }のように書けば、ブロックの部分がHtml型として渡されるらしい。
@(hensu: String)
@main("ここがタイトル") {
<p>このブロックがcontentに渡る</p>
}
これでmain.scala.htmlの@titleや@contentのとこに、渡した引数が反映される感じのHTMLが表示された。
フォームを作って2つの数値をpostし、結果を足し合わせて返すという高度なアプリを作ってみる。
まず、app/controllers/Foo.scalaに足し算用のメソッドを追加します。本当はもっと良い書き方があるけど(helloworldのControler参照)、行数を費やしたくないので今回はこんな感じで。
def add = Action { req => {
val num1 = req.body.asFormUrlEncoded.get("num1")(0).toInt
val num2 = req.body.asFormUrlEncoded.get("num2")(0).toInt
Ok( views.html.add( num1 + num2 ) )
} }
見ての通り、リクエストされた内容からnum1とnum2の値を足して引数で渡しています。
なので、num1とnum2を渡してくれるフォーム画面が必要です。
app/views/foo.scala.htmlに、Formを書いてみます。
@(hensu: String)
@main(hensu) {
<form action="/add" method="post">
<input type="text" name="num1"> + <input type="text" name="num2">
<input type="submit" value="足す">
</form>
}
次にapp/views/add.scala.htmlというファイルを作って、足し算結果を表示するviewを書きます。
@(result: Int)
@main("答え") {
答えは @result です
}
最後に、conf/routesに、今回追加したadd用の設定を追記します。
POST /add controllers.Foo.add
これでhttp://localhost:9000/fooにリクエストして、フォームに数値を入力してSubmitすると、「答えは〜です」という結果が表示されます。
バリデーションも何もしていない、実にかっこいい機能の出来上がり。