PlayFramework/チュートリアル+α の履歴(No.1)

更新


公開メモ

Play Framework

http://www.playframework-ja.org/

Java あるいは Scala ベースで開発できる軽量 Web アプリケーションフレームワークだそうで、 面白そうだったのでさわってみました。

Debian squeeze 上で、環境は

LANG:console
$ java -version
java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8.13) (6b18-1.8.13-0+squeeze2)
OpenJDK Client VM (build 14.0-b16, mixed mode, sharing)

です。

Scala を開発言語としていじってみます。

以下、私自身は Ruby on Rails の経験があったため、 この手のフレームワークを始めてさわる人向けではない記述になっていると思いますが、 ご了承下さい。

インストール&チュートリアル

Java 環境が揃っていれば、あとは .zip ファイルを落としてきて展開するだけというお手軽インストールで、

Play 2.0 のインストール
http://www.playframework-ja.org/documentation/2.0.4/Installing

初めての Play アプリケーション (Scala 版)
http://www.playframework-ja.org/documentation/2.0.4/ScalaTodoList

を見れば、チュートリアルを追うところまでは迷うところはありませんでした。

  • コンパイルが必要な言語で開発を行っているにもかかわらず、ブラウザで再読込するだけで ソースの変更を自動で検出してシームレスにアプリケーションをアップデートしてくれる
  • その際、コンパイルエラーもブラウザ上で確認可能
  • エボルーション(Rails におけるマイグレーション)までブラウザから行える
  • 生の SQL を基本としているので参入障壁が低い

あたりはさすが後発なだけあると思いました。

チュートリアルに手を加える

以下、さらにもう少し様子をうかがうために、チュートリアルで作成した todolist アプリケーションに機能を追加してみることにします。

task に登録日時を記録する

データベースに該当するカラムを追加

データベース形式の変更は、その履歴をちゃんとバージョン管理するために、 エボルーションを利用します。

conf/evolutions/default/2.sql

LANG:sql
# Tasks schema
 
# --- !Ups

ALTER TABLE task ADD COLUMN registered DATETIME NOT NULL;
 
# --- !Downs
 
ALTER TABLE task DROP COLUMN registered;

チュートリアルのまま H2 を使っているので、SQL 文法は
http://www.h2database.com/html/main.html
を参考にしました。

ブラウザから todolist ページを再読込すると、

Database 'default' needs evolution!
An SQL script will be run on your database - [Apply this script now!]

としてエボルーションするかどうかを聞かれるので、 [Apply this script now!] を押すと、データベースが更新される。

モデルの変更

クラスに対応するメンバーを追加する。

app/models/Task.scala

LANG:scala
case class Task(
    id: Long, 
    label: String,
    registered: java.util.Date
)

registered の型は NULL を許容するのであれば Option[java.util.Date] とするらしいけれど、 今の場合は NOT NULL を指定してあるので生の java.util.Date とした。

Anorm パーサーの変更

データベースの行データを Task クラスのインスタンスへ変換するためのパーサーを registered に対応するよう書き直します。

Anorm によるシンプルなデータアクセス
http://www.playframework-ja.org/documentation/2.0.4/ScalaAnorm
の「Parser API の利用」を参照しました。

app/models/Task.scala

LANG:scala
object Task {

  val task = {
    get[Long]("id") ~
    get[String]("label") ~
    get[java.util.Date]("registered") map {
      case id~label~registered => Task(id, label, registered)
    }
  }
  
  def all(): List[Task] = DB.withConnection { implicit c =>
    SQL("select * from task").as(task *)
  }
 

val task の部分でパーサーを定義して、それを all() の中の .as(task *) の部分で使っています。

SQL("select * from task") が返す行データを task パーサーで変換して List[Task] 型として返しているのが all() になります。

task の中身は、id, label, registered の値を使って Task(id, label, registered) を作成して返しているのですが、 私自身この部分の細かい文法はまだ理解できていません。

とりあえず呪文としては基本的な使い方を理解しました。

INSERT 文に registered を与える

registered に NOW() を与えるだけです。

app/models/Task.scala

LANG:scala
  def create(label: String) {
    DB.withConnection { implicit c =>
      SQL(
        """
            INSERT INTO task (label, registered) VALUES ({label}, NOW())
        """
      ).on(
        'label -> label
      ).executeUpdate()
    }
  }

一覧に登録日を表示する

app/views/index.scala.html

LANG:scala
@main("Todo list") {
    
    <h1>@tasks.size task(s)</h1>
    
    <ul>
        @tasks.map { task =>
            <li>
                @task.label (@task.registered)
                
                @form(routes.Application.deleteTask(task.id)) {
                    <input type="submit" value="Delete">
                }
            </li>
        }
    </ul>

Counter: 8432 (from 2010/06/03), today: 3, yesterday: 1