読者です 読者をやめる 読者になる 読者になる

argius note

プログラミング関連

開発しています



Play!2.0始めました(1)

Scala

Scala(&Java)で利用可能な" Play framework 2.0 "を始めてみました。
今回は、2012-05-02にリリースされた、バージョン2.0.1を使っています。

  • 簡単な紹介
  • 文字列の外部化と国際化
  • Viewのビルド後のリフレッシュ
  • 他のページにエラーがあると全部が使えない
  • Cygwinで使えない?
  • SUB-URIのprefixオプション

Play framework(以下Play)は、フルスタックのWebアプリケーションフレームワークです。

特色やセールスポイントについては、ホームページでアピールされているので、そちらを参照にしていただくとして...
ドキュメントが分かりやすいし、とっつきやすい感じでいいですね。(ただやっぱりScalaは貧弱なmyPCでは重いので、Scalaのためにハイスペックなのを所望。)



個人的な注目点は、下記のとおり。

  • フルスタック
  • Mavenが標準でない(SBT*1が標準)
  • ステートレスが基本
  • ViewテンプレートがScalaベース*2
  • 完全なJavaとScalaのハイブリッド(テンプレート以外)

Scalaで分からないところがあっても、Javaで作っておき、あとで置き換えるといったこともできそうです。Javaに慣れていてScala初心者には歓迎すべきところではないでしょうか。
DBアクセスのところなんかは、簡潔で直感的な記述ができるのが特に良いです。



2.0になって、Scalaが完全に統合されています。これについて、公式サイト(トップページの最下段)にアーキテクチャの変遷についての説明があります。
普通のFWなら単なる設定ファイルとするところを、一部をScalaベース(テンプレート、routesファイルなど)にしてしまうなど、静的型付けに深いこだわりがあるようですね。



以下は、練習で作ってみた、社員リストのコードです。

  • app/models/Employee.scala
package models

import anorm.{toParameterValue, sqlToSimple, SQL}
import anorm.SqlRow
import play.api.Play.current
import play.api.db.DB

class Employee {
  var number: String = ""
  var name: String = ""
}

object Employee {

  def read(row: SqlRow): Employee = {
    val o = new Employee
    o.number = row[String]("number")
    o.name = row[String]("name")
    return o
  }

  def findLast(limit: Int) = {
    // dspost=postgresのデータソースID
    DB.withConnection("dspost") { implicit c =>
      SQL(
        """
          SELECT number, name
          FROM employee WHERE disabled=false
          ORDER BY updated DESC
          limit {limit}
        """).on('limit -> limit)().map(row => read(row)).toList
    }
  }

}
  • app/controllers/Application.scala
package controllers

import models.Employee
import play.api.mvc.{Controller, Action}

object Application extends Controller {

  def home = Action { request =>
    Ok(views.html.employeelist(Employee.findLast(30))(lang(request)))
  }

}
  • app/views/employeelist.scala.html
@(rows: Seq[Employee])(implicit lang: Lang)

<!DOCTYPE html>
<html>
<body>
<ol>
  @for(row <- rows) {
  <li>@row.number: @row.name</li>
  }
</ol>
</body>
</html>


そのほか、使ってみていくつか気づいたことをメモします。
環境:

文字列の外部化と国際化

伝統的な*.propertiesとnative2asciiではなく、UTF-8のテキストファイルを使います。
私もちょうどこの仕組みに切り替えたところなので、ちょっとうれしい。

ただ、仕組み上しかたないとは言え、言語の判定がちょっと面倒。
Controllerのヘルパーメソッド"lang(request)"を使えば、"Accept-Languages"から使用する言語を取り出すことができますが、これをControllerからViewに明示的に渡す必要があるのです。

// Controller側
  def search(cond: String): Seq[Record] = {...}
  def init(cond: String) = Action { request =>
    Ok(views.html.index(search(cond))(lang(request)))
  }

// View側
@(rows: Seq[Record])(implicit lang: Lang)
@Messages("reload")

デフォルトで"Accept-Languages"による判定になってくれたほうがうれしいような。
主開発者の方がen言語圏でない(氏の個人サイト)ので、その辺のご理解はあるのではないかと期待します。

Viewのビルド後のリフレッシュ

Viewのhtmlは、classファイルにコンパイルされてから実行されます。
これらはクラスフォルダー(target/scala-2.9.1/classes_managed)でビルドパスに設定されます。
Viewのパラメータを変えた後でControllerのView呼び出し修正すると、Eclipseには更新が直接反映されないので、エラーになってしまいます。
これは、"play eclipsify"、プロジェクトのクリーン(メニュー→プロジェクト→ビルド)、プロジェクトのリフレッシュ、ビルドパスの再設定、のどれかを試すと良いです。(どれが正解か、まだ良く分かりません。)

他のページにエラーがあると全部が使えない

すべてのファイルをコンパイルするので、ページを表示しようとしたとき、そのページ以外にエラーがあった場合でも、コンパイルエラーのためエラー画面が表示されてしまいます。
これはちょっと不便かも。

Cygwinで使えない?

これについては、別エントリにまとめました。

SUB-URIのprefixオプション

リバースプロキシ経由でコンテキストパスを変えたい場合に、他のプロダクトにあるようなprefixオプションが欲しいです。
Gitリポジトリには入っているらしいですが、2.1ではサポートされるのでしょうか。期待です。


注文をたくさんつけてしまいましたが、総合的には好感触なので、これからも期待しています。
まだ試せていないことがたくさんあるので、それについてはまた改めて。

*1:Simple Build Tool: Scala用のビルドツール

*2:以前のバージョンだとGroovyを覚える必要がありました。