§初めての Play アプリケーション
Play 2.0 でシンプルな TODO 管理アプリケーションを構築し、クラウドにデプロイしましょう。
§前提条件
はじめに、 Play のインストールが成功している 事を確かめてください。 Java (バージョン 6 以上) のインストールと、Play のバイナリパッケージの unzip さえすれば始められます。
多くのコマンドを使用するため、 Unix 系 OS を利用する方が効率が良いです。 もし Windows システムを動作させているなら、コマンドプロンプトにいくつかのコマンドを入力する事で、動作させる事も可能です。
もちろん、テキストエディタは必要です。 Eclipse もしくは IntelliJ のようなお好みの Scala IDE を利用することもできます。しかし、 Playであれば、編集や開発プロセス自身をフレームワークが管理してくれるため、 Textmate, Emacs もしくは vi のようなシンプルなテキストエディタで楽しみながら作業する事が可能です。
§プロジェクト作成
Play が正しくインストールされているとして、新しくアプリケーションを作成してみましょう。 Play アプリケーションを作成する事はとても簡単であり、 Play コマンドで全て管理されています。全ての Play アプリケーション間で標準のプロジェクト構成になります。
コマンドラインで以下のように入力してください:
$ play new todolist
いくつかの質問が表示されます。 Create a simple Scala application プロジェクトのテンプレートを選択しましょう。
play new
コマンドが新しいディレクトリ todolist/
を作成し、一連のファイル、ディレクトリを生成します、重要なファイルは以下の通りです:
app/
ディレクトリには models 、 controllers 、そして views ディレクトリに分かれたアプリケーションのコアが入っています。本ディレクトリには .scala ソースファイルが入っています。conf/
ディレクトリには全てのアプリケーションの設定ファイル (特にメインとなるapplication.conf
ファイル、routes
定義ファイル、国際化のためのmessages
ファイル) が入っています。project
ディレクトリにはビルドスクリプトが入っています。ビルドシステムは sbt に基づいています。新しい Play アプリケーションはアプリケーションを正常動作させるデフォルトのビルドスクリプトが同梱されています。public/
ディレクトリには全てのパブリックに利用可能なリソース (JavaScript 、スタイルシート、画像イメージ) が入っています。test/
ディレクトリにはアプリケーションのテストが入っています。 テストは Specs2 の仕様で書かれています。
Play では UTF-8 をエンコーディング形式として採用しており、上記のディレクトリ内に配置された全テキストファイルは本文字セットを使用してエンコードされます。テキストエディタに応じて設定を確認してください。
§Play コンソールの使用
一度アプリケーションを作成したら、 Play コンソールを実行できます。新しく作成された todolist/
ディレクトリに行き、以下のコマンドを実行して下さい:
$ play
このコマンドで Play コンソールが起動されます。 Play コンソールから実行できることはいくつかありますが、まずアプリケーションを実行させてみましょう。コンソールプロンプトから、 run
とタイプしてください:
[todolist] $ run
development モードで本アプリケーションが実行されています。ブラウザを開き、 http://localhost:9000/ へアクセスしてください:
補足: 詳細は Play 2.0 コンソールを使う を確認してください。
§概要
このアプリケーションがどうやってページを表示しているか確認して行きましょう。
本アプリケーションの主なエントリーポイントは conf/routes
ファイルです。このファイルはアプリケーションがアクセス可能な URL の全てを定義しています。生成された routes ファイルを開けば、初期の route を確認できます:
GET / controllers.Application.index
上記の記述は web サーバーが / パスへの GET リクエストを受信した時に、 controllers.Application.index
メソッドから実行するための Action
を検索するという事を簡単に説明しています。
どうやって controllers.Application.index
メソッドが実行されるのかを確認しましょう。 todolist/app/controllers/Application.scala
ソースファイルを開いてください:
package controllers
import play.api._
import play.api.mvc._
object Application extends Controller {
def index = Action {
Ok(views.html.index("Your new application is ready."))
}
}
controllers.Application.index
はリクエスト処理を扱う Action
型の戻り値を返す事が確認できます。 Action
は web ブラウザへの HTTP レスポンスを表現する Result
を返す必要があります。
補足: Action についての詳細は、 アクション、コントローラ、レスポンス を確認してください。
ここでは、アクションは HTML コンテントを含む 200 OK のレスポンスを返します。 HTML コンテントはテンプレートによって提供されます。 Play テンプレートは標準 Scala 関数、 views.html.index(message: String)
としてコンパイルされます。
本テンプレートは app/views/index.scala.html
ソースファイル上で定義されます。
@(message: String)
@main("Welcome to Play 2.0") {
@play20.welcome(message)
}
最初の行は関数の仕様を定義します。ここでは、単一の String
型のパラメータを受け取ります。テンプレートの内容には Scala と HTML (もしくはテキストベースの言語) を一緒に記入しています。 Scala の式は特殊な @
文字から始まります。
§開発フロー
さて、新しいアプリケーションにいくつかの変更を加えていきましょう。 Application.scala
内でレスポンスの内容を書き換えてみます:
def index = Action {
Ok("Hello world")
}
上記の変更によって index アクションはシンプルな text/plain
の Hello world レスポンスを返すようになります。この変更を確かめるために、ブラウザ上でホームページを更新してみましょう:
変更を確認するのにコードを明示的にコンパイルする必要もサーバーを再起動する必要もありません。変更が検出されると、自動的にリロードされます。もし、コードを誤って変更したらどうなるでしょう?
やってみましょう:
def index = Action {
Ok("Hello world)
}
上記のように変更したら、ブラウザ上でホームページをリロードしてください:
確認された通り、ブラウザ上に直接エラーが表示されます。
§アプリケーションの準備
本 TODO 管理アプリケーションのために、いくつかのアクションと一致する URL が必要になります。 routes ファイルを定義することから始めましょう。
conf/routes
ファイルを編集してください。
# Home page
GET / controllers.Application.index
# Tasks
GET /tasks controllers.Application.tasks
POST /tasks controllers.Application.newTask
POST /tasks/:id/delete controllers.Application.deleteTask(id: Long)
全てのタスクを一覧する route 定義を作成し、タスクの作成と削除を処理する2つの定義を作成します。タスク削除を扱うための route には URL パス内に id
変数を定義しています。 id
の値は Action
を作成する deleteTask
メソッドへ渡されます。
ブラウザをリロードすれば、 Play が routes
ファイルをコンパイルできないことが確認できます。
これは新しい routes ファイルが参照しているアクションメソッドが存在していないことが原因です。 Application.scala
ファイルに追記していきましょう。
object Application extends Controller {
def index = Action {
Ok("Hello world")
}
def tasks = TODO
def newTask = TODO
def deleteTask(id: Long) = TODO
}
上記のコードでアクションの暫定的な実装をするために TODO
という定義を使用しました。まだアクションの実装を書きたくない場合に、ビルドインの TODO
アクションを使用することが可能です。このアクションからは 501 Not Implemented
HTTP レスポンスが返るようになっています。
確認するために、 http://localhost:9000/tasks にアクセスできるか試してみましょう。
アクションの実装をはじめる前に修正が必要な最後の項目は index
アクションです。タスクの一覧ページに自動リダイレクトするようにします:
def index = Action {
Redirect(routes.Application.tasks)
}
見ての通り、 303 See Other
の HTTP レスポンスを使用するために Ok
の代わりに Redirect
を使用しています。また、tasks
アクションの呼び出しに必要な URL を生成するため、リバースルーターを使っています。
補足: 詳細については HTTPルーティング を参照してください。
§Task
モデルの準備
実装を続ける前にアプリケーション内で Task
の見え方を定義する必要があります。 case class
を app/models/Task.scala
ファイルに作成してください:
package models
case class Task(id: Long, label: String)
object Task {
def all(): List[Task] = Nil
def create(label: String) {}
def delete(id: Long) {}
}
Task
の操作を管理するためのコンパニオンオブジェクトも上記のように作成しました。ここでは各操作のダミー実装を記載しましたが、このチュートリアルの後半、リレーショナルデータベースに関する項目の中でタスクを保存する実装を追加していきます。
§アプリケーションテンプレート
本アプリケーションはタスクリストとタスク作成フォームの両方を見せる単一のウェブページを用います。index.scala.html
テンプレートを変更していきましょう:
@(tasks: List[Task], taskForm: Form[String])
@import helper._
@main("Todo list") {
<h1>@tasks.size task(s)</h1>
<ul>
@tasks.map { task =>
<li>
@task.label
@form(routes.Application.deleteTask(task.id)) {
<input type="submit" value="Delete">
}
</li>
}
</ul>
<h2>Add a new task</h2>
@form(routes.Application.newTask) {
@inputText(taskForm("label"))
<input type="submit" value="Create">
}
}
2つのパラメータを受け取るように以下のテンプレートのシグネチャを変更しました:
- 表示用のタスクのリスト
- タスクのフォーム
フォーム作成用のヘルパー関数を提供する helper._
をインポートしました。これにより、 action
や method
属性がついた <form>
タグ付きの HTML を生成する form
関数や入力用のフォーム HTML を作成する inputText
関数が使用できます。
補足: 詳細については テンプレートエンジン と フォームテンプレートヘルパーの利用 を確認してください。
§タスクフォーム
Form
オブジェクトはバリデーションを含んだ HTML フォームの定義をカプセル化します。本アプリケーションでは単一の label フィールドを持つフォームが必要なため、フォームを Application
コントローラ内に作成してみましょう。フォームは以下のようにする事で、ユーザーによって入力された label が空ではないことをチェックします。
import play.api.data._
import play.api.data.Forms._
val taskForm = Form(
"label" -> nonEmptyText
)
taskForm
の型はシンプルな String
を生成するフォームである事を表す Form[String]
型になります。 play.api.data
内にあるいくつかのクラスをインポートする必要があります。
補足: 詳細は フォームの定義 を参照してください。
§最初のページをレンダリングする
現在、アプリケーションページを表示するために要求された全ての要素の用意ができました。 tasks
アクションを実装しましょう:
import models.Task
def tasks = Action {
Ok(views.html.index(Task.all(), taskForm))
}
本メソッドがタスクリストとタスクフォームを呼出し、 index.scala.html
テンプレートによってレンダリングされた HTML を含む 200 OK の結果を返します。
ブラウザ内で http://localhost:9000/tasks にアクセスしてみましょう:
§フォーム投稿処理
現在では、タスク作成フォームを投稿した場合でも、まだ TODO ページが取得されます。 newTask
アクションの実装をしていきましょう。
def newTask = Action { implicit request =>
taskForm.bindFromRequest.fold(
errors => BadRequest(views.html.index(Task.all(), errors)),
label => {
Task.create(label)
Redirect(routes.Application.tasks)
}
)
}
フォームに入力するため、スコープ内で request
を持つことが必要になります。その場合、リクエストデータが入力されている新しいフォームを作成する bindFromRequest
によってそれを扱うことができます。もしフォーム内に何らかのエラーがあった場合、エラーを再表示します (ここでは、 200 OK の代わりに 400 Bad Request を使用します) 。もし何のエラーも発生しなければ、タスクを作成し、タスクリストにリダイレクトします。
補足: 詳細は フォーム送信を扱う を参考にしてください。
§データベース内のタスクを永続化する
アプリケーションを使いやすくするため、データベースにタスクの情報を永続化するようにしましょう。本アプリケーション内でデータベースを利用可能にします。 conf/application.conf
ファイル内に以下の内容を追記してください:
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
現在、 H2 というシンプルなインメモリデータベースを使用するようにしています。この定義を有効にするためにサーバーを再起動する必要はありません、ブラウザを更新するだけでデータベースをセットアップします。
これからチュートリアル内でデータベースを検索するために Anorm を使用します。はじめにデータベーススキーマを定義する必要があります。 conf/evolutions/default/1.sql
に最初のエボリューションスクリプトを作成し、 Play エボリューション機能を使っていきましょう。
# Tasks schema
# --- !Ups
CREATE SEQUENCE task_id_seq;
CREATE TABLE task (
id integer NOT NULL DEFAULT nextval('task_id_seq'),
label varchar(255)
);
# --- !Downs
DROP TABLE task;
DROP SEQUENCE task_id_seq;
ブラウザを更新すれば、 Play はデータベースがエボリューションを要求していることを警告するでしょう。
Apply script ボタンをクリックするだけで、スクリプトが実行され、あなたのデータベーススキーマは定義済みになります!
補足: エボリューションの詳細については エボリューション を参照してください。
all()
操作を実行するための SQL クエリーを Task
コンパニオンオブジェクト内に実装していきましょう。 Anorm を使用することで、 JDBCの ResultSet
の一行を Task
の値に変換するパーサーを定義することができます。
import anorm._
import anorm.SqlParser._
val task = {
get[Long]("id") ~
get[String]("label") map {
case id~label => Task(id, label)
}
}
ここでは、少なくとも id
や label
列を持つ JDBC の ResultSet
の一行が与えられ、 Task
の値を作成することが可能な task
というパーサーになります。
all()
メソッドの実装を構築するパーサーを使用することができます:
import play.api.db._
import play.api.Play.current
def all(): List[Task] = DB.withConnection { implicit c =>
SQL("select * from task").as(task *)
}
自動的に JDBC 接続の作成と解放を行う Play の DB.withConnection
ヘルパーを使用します。
クエリーを作成するために、 Anorm の SQL
メソッドを使用します。 as
メソッドが task *
パーサーを用いて ResultSet
をパースする事ができるようになります。そのため一度に多くのタスク行をする場合は、一割でパースし、 List[Task]
を返り値に戻します (しかし、ここでの task
パーサーは Task
を戻します。)
実装を完成させる時が来ました:
def create(label: String) {
DB.withConnection { implicit c =>
SQL("insert into task (label) values ({label})").on(
'label -> label
).executeUpdate()
}
}
def delete(id: Long) {
DB.withConnection { implicit c =>
SQL("delete from task where id = {id}").on(
'id -> id
).executeUpdate()
}
}
本アプリケーションで遊んでみましょう、新しいタスク作成は動作すると考えられます。
補足: Anormの詳細は Anorm を参照してください。
§タスクの削除
タスクを作成することができるようになった後はそれらのタスクを削除する事ができるようにする必要があります。 deleteTask
アクションの実装を (とてもシンプルに) 終わらせるだけです。
def deleteTask(id: Long) = Action {
Task.delete(id)
Redirect(routes.Application.tasks)
}
§Heroku へのデプロイ
全ての機能が完成しました。本アプリケーションを成果物としてデプロイしましょう。 Heroku へデプロイします。はじめに Procfile
を Heroku 用に作成する必要があります。 Procfile
をアプリケーションの root ディレクトリに作成してください。
web: target/start -Dhttp.port=${PORT} -DapplyEvolutions.default=true -Ddb.default.url=${DATABASE_URL} -Ddb.default.driver=org.postgresql.Driver
補足: Heroku へのデプロイの詳細については Heroku へのデプロイ を参照してください。
Heroku 上で起動する際に、アプリケーションの設定を上書きするためにシステムプロパティを使用します。 Heroku は PostgreSQL データベースを提供しているため、 要求されたドライバーをアプリケーションの依存関係に追加する必要があります。
project/Build.scala
ファイルに依存関係を明記してください。
val appDependencies = Seq(
"postgresql" % "postgresql" % "8.4-702.jdbc4"
)
補足: 依存性の管理については 依存性の管理 を参照して下さい。
Heroku はアプリケーションのデプロイに git を使用します。まずは git リポジトリを初期化しましょう。
$ git init
$ git add .
$ git commit -m "init"
これで Heroku 上にアプリケーションを作ることが出来ます。
$ heroku create --stack cedar
Creating warm-frost-1289... done, stack is cedar
http://warm-1289.herokuapp.com/ | [email protected]:warm-1289.git
Git remote heroku added
さらに git push heroku master
を使ってアプリケーションをデプロイします。
$ git push heroku master
Counting objects: 34, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (20/20), done.
Writing objects: 100% (34/34), 35.45 KiB, done.
Total 34 (delta 0), reused 0 (delta 0)
-----> Heroku receiving push
-----> Scala app detected
-----> Building app with sbt v0.11.0
-----> Running: sbt clean compile stage
...
-----> Discovering process types
Procfile declares types -> web
-----> Compiled slug size is 46.3MB
-----> Launching... done, v5
http://8044.herokuapp.com deployed to Heroku
To [email protected]:floating-lightning-8044.git
* [new branch] master -> master
Heroku はアプリケーションを構築し、クラウド上のどこかのノードにデプロイします。アプリケーションのプロセス状態を確認できます。
$ heroku ps
Process State Command
------------ ------------------ ----------------------
web.1 up for 10s target/start
起動していたら、ブラウザで開いてみましょう。
あなたの初めてのアプリケーションがアップロードされ、稼働しています!