= ステップバイステップ =
以下の記述は、Play1.2.2RC2+scala-0.9.1によるもの(2011/6/25時点)
※play-scala-0.9とはコントローラの記述方法が大幅に異なるので注意。
== 基本操作 ==
=== スケルトンの作成と実行 ===
上述の環境でplay new ... -with scalaとしてアプリケーションを作成する。
さらにIntelliJで編集できるように、play idealize ...などとする(このときエラーが発生するがとりあえず無視)。
play runとして実行する。通常通りのデモ画面が表示される。
ちなみに、現時点ではplay run --%prodとするとエラーが発生してしまう。
[[Play/Trouble|トラブルと解消]]を参照のこと。
=== ルーティングの確認 ===
conf/routesを確認すると、
{{{
GET / Application.index
}}}
という記述がある。
これは、ルートにアクセスされた場合には、controllersパッケージのApplicationオブジェクトのindexメソッドを呼び出すという意味。
ここで記述する、オブジェクト名はcontrollersパッケージに存在することを前提としている。これはJavaの命名規約に背くものであるが、このアプリ内だけで使うものなので問題ないだろう、と考えたものと思われる。
controllers以外のパッケージのものを使う場合は、パッケージ名を明示する必要がある。
routesの記述はJava版と同じと考えられる。
[[http://playdocja.appspot.com/documentation/1.2/routes#syntax|routes ファイルの構文]]
=== コントローラの確認 ===
app/controllers.scalaを見てみると、
{{{
package controllers
import play._
import play.mvc._
object Application extends Controller {
import views.Application._
def index = {
html.index("Your Scala application is ready!")
}
}
}}}
などとなっている。この「html.index("Your Scala application is ready!")」という部分は、play-scala専用のテンプレートエンジンによって生成される関数の呼び出しなのだが、これはとりあえず無視する。
とりあえず、テンプレートは使わずに自力でhtmlを作成して返すことを考える。
=== 自力HTMLの返し方 ===
次のように記述を変更する。
{{{
object Application extends ScalaController {
def index = {
Html("
hello, wold
")
}
}
}}}
まず、「extends Controller」は「extends ScalaController」に変更する。前者のままだとIntelliJが混乱してしまうようなので
(-->現在のeclipse用scalaプラグインでは問題無い模様)。
さらに、indexの本文にはhtmlらしきものを引数としてHtml()というメソッドを呼び出してみる。もちろんScalaでは直接XMLが記述できるので、二重引用符はなくてもよい。
この状態でIntelliJの「Goto Declaration」機能を使えば、ScalaControllerやHtmlの宣言部分にジャンプすることができる(見たいものを右クリックして表示されるメニューで「Goto」「Declaration」)。
このような「アクションメソッドの返り値」には、ほかにも次のようなものがある。
[[http://www.playframework.org/modules/scala-0.9.1/controllers#Actionmethodsreturnvalues]]。
=== パラメータの受け取り方 ===
アクションメソッドの引数として、パラメータ(の内容を受け取る引数)を指定できる。例えば、
{{{
def index(name: String, address: String) = {
Html({name + " --- " + address}
)
}
}}}
とすると、
{{{
http://localhost:9000/?name=sugimura&address=tokyo
}}}
というURLの呼び出しで
{{{
sugimura --- tokyo
}}}
と表示される。しかしこのやり方では、「プログラム上でパラメータ名を生成する」ことができなくなる。この場合には、以下のように記述する。
{{{
def index() = {
val name = params.get("name")
val address = params.get("address")
Html({name + " --- " + address}
)
}
}}}
このあたりもJava,Scalaで共通の模様。
[[http://playdocja.appspot.com/documentation/1.2/controllers#params]]
=== Scala版特有のパラメータハンドリング ===
おそらくJava版には無い、Scala版特有のものとして便利な処理方法がある。例えば以下のように記述できる。
{{{
def index(name: Option[String], address: String = "Japan") = {
Html({name.getOrElse("gonbei") + " --- " + address}
)
}
}}}
あるいは、次のような書き方もある。
{{{
case class User(name: String, address: String)
def index(user: User) = {
Html({user.toString()}
)
}
}}}
が、パラメータはuser.nameなどとする必要がある。
{{{
http://localhost:9000/?user.name=sugimura&user.address=tokyo
}}}
参考は[[http://www.playframework.org/modules/scala-0.9.1/dataBinding]]
=== URLの取得 ===
データバリデーションなどは自力でもできる(自力でやった方が効率が良い場合も多い)のでとりあえず省略。
他のリソースを参照する、つまりURLを取得するにはどうするか。
routesを以下にする。
{{{
GET / Application.index
GET /login Application.login
}}}
Applicationオブジェクトを次のように書く。
{{{
object Application extends ScalaController {
def index() = {
Html(
"to login page"
)
}
def login() = {
Html(enter id and password...)
}
}
}}}
つまり、Router.reverse(アクション名)とすることにより、そのURLが取得できる
(このやり方はよろしくない。オブジェクト名やメソッド名を文字列で指定しているから)。
ただし、scala templatesを使えばもっと簡単になる。
=== リダイレクト ===
単純にRedirect(リダイレクト先URL)を返せばよい。
{{{
def index() = {
if (!isLoggedIn) {
Redirect("login")
} else {
...
}
}
}}}
RedirectはScalaControllerの中で定義されたメソッドで、以下の定義
{{{
def Redirect(url: String) = new Redirect(url)
}}}
== テンプレート ==
とりあえずは、前述した知識があればplay-scala上のwebアプリケーションを作成することができると思われるが、
しかし、大規模なウェブアプリをテンプレートエンジン無しに行うのは面倒である。特に生成するhtmlの中に静的部分が大量に含まれる場合や、デザイナさんと共同作業する場合は。
もちろん、他のテンプレートエンジン(freemarkerやvelocity)を使うこともできるはずだが、ここでは新しく開発されたというscala templatesを試してみる。
=== scala templates ===
scala-0.9.1より、playの従来のテンプレートエンジンとは異なる、scala templatesというものが導入された。
従来のものはテンプレート上の言語がなぜかgroovyであり、テンプレートの入れ子指定も妙な構文だったが、それらは一新され、htmlとscalaのみで記述できるようになっている(ようだ)。
scala templatesの基本的な考え方は以下(と思われる)。
* scalaとhtmlをまぜこぜにしたファイル(テンプレート)を作成する。
* これが実行時に自動的にscala関数に変換される。
* コントローラ(Application.indexメソッドなど)からはこれを関数として呼び出す。
* もちろん、テンプレートにはscalaの式が記述できるので、テンプレート内から別のテンプレートを呼び出すこともできる。
微妙な点としては
* テンプレート「関数」は実行時に作成されるので、IDE上では常に「そんな関数はねえ」というエラーを見続けることになる。
* (そんな人はいないだろうけれども)play-scala環境以外では動作しない。
と思ったが、一度でもrunすればテンプレートから自動生成したscalaソースがtmp/generatedディレクトリに格納されるので、それを参照すればよいようだ。
=== 名前のつけ方 ===
もちろん互換性のためと思われるが、名前のつけ方が微妙にめんどくさい。
* テンプレートのファイル名はすべて「.scala.html」という拡張子をもたなければならない(ようだ)。
* テンプレートから生成されたscalaコードのパッケージは、テンプレートの存在するディレクトリパス+htmlになる。例えば、「app/views/Application/index.scala.html」というテンプレートからは(appは省かれて、htmlが追加され)viewer.Application.htmlというパッケージのindexというオブジェクトになる。
=== テンプレートから他テンプレートの呼び出し ===
テンプレートから他テンプレートを呼び出す場合には、scalaと同じく次の規則がある。
* 同じパッケージにある(すなわち同じディレクトリにある)テンプレートは名前のみで呼び出せる。
* 他のパッケージにあるテンプレートはパッケージ名を明示するか、あるいはimportを使わなければいけない。
* ただし、viewsパッケージ(viewsディレクトリ)のものは名前だけでも呼び出せる。
importを使う場合は、(テンプレートはscalaコードに変換されるので)scalaから呼び出す場合と同じく、お尻にはhtmlがつく。例えば、views/Application/Otherにあるsome.scala.htmlを呼び出したい場合は、
{{{
@import views.Application.Other.html._
@some(...)
}}}
などとしなければならない。
最後の規則は、テンプレートから生成されたscalaコードに自動的に
{{{
import views.html._
}}}
がつけられるというもの。
※この規則がゆえに、スケルトンコードのindex.scala.htmlでは、パッケージを指定せずにmain.scala.htmlが呼び出せている。こういう事情はきちんと書いておくべきかと思うのだが。。。。
=== テンプレートの書き方 ===
さて、テンプレートの書き方は以下で説明されている。
* [[http://www.playframework.org/modules/scala-0.9.1/templates|Scala templates]]
* [[http://playscalaja.appspot.com/documentation/0.9.1/templates|Scala テンプレート(上の訳)]]
* [[http://d.hatena.ne.jp/sy-2010/20110530/1306770086|play-scalaを改めて学ぶ-15 Scala templates その1]]
* [[http://d.hatena.ne.jp/sy-2010/20110531/1306832074|play-scalaを改めて学ぶ-16 Scala templates その2]]
要するに@の後にScalaのコードを書くということであるが、この有効範囲がどうにもわからない。
例えばforの後の中括弧はhtmlを記述する場所であるが、matchの後の中括弧はcaseを記述する場所と、まちまち。
この理由はテンプレートコンパイラのソースを見るとわかる(modules/scala-0.9.1/src/play/templates/ScalaTemplate.scala)。forやmatchなどのキーワードごとに処理を変更しているのだ。
だから、上のマニュアルの「書き方」はまるごと覚えなければならない。もちろん、常識的なルールになっているようではあるが。
=== ビルトインメソッド ===
マニュアルには記述がないのだが、テンプレート中で使えるビルトインメソッドとでもいうべきものが用意されている。
これらが無くては不便でしょうがないので、きちんとマニュアル化されるべきだろう。
これを調査するために、既存のテンプレートから生成されたscalaコードを調べてみる。以下のようなimportが挿入されている。
{{{
import play.templates._
import play.templates.TemplateMagic._
import views.html._
}}}
「import views.html._」については先に述べた。
他のimport先のソースはmodules/scala-0.9.1/src/play/templates/ScalaTemnplate.scala
になる。
特にTemplateMagicが重要で、ここにactionとassetという二つのメソッドがある。これらの二つを使って他のリソースのURLを指定することができる。
例えば、Application.listというメソッドが何らかのリスト表示メソッドであれば、
{{{
リスト表示
}}}
※もちろん、routesにも記述がなければならない。
assetは
{{{
}}}
などとする。これももちろんroutesに適切な記述がなければいけない。
=== エスケープに関するバグ ===
デフォルトで文字列のhtmlエスケープがされるが、play-scala 0.9.1ではエスケープ処理にバグがある。
また、エスケープさせないための記述は少々面倒。
[[http://ruimo.com/2011/09/01/1314830640000.html]]を参照のこと
=== 評価 ===
テンプレート内で別の言語ではなく、scalaがそのまま使えるというのは多いに評価できる点であるが、しかし先に述べたように、@以降のScalaコードの有効範囲のルールがわかりづらい。いくつか記述してみないと感覚がつかめないかもしれない。
また、この方式のテンプレートは、Wicket等のものとは異なり、「htmlのカタチ」はしていないので、デザイナとの共同作業は不向きであると思われる。あくまでも技術者向けではないだろうか。しかし、そうであるなら、個人的には、scalaの生文字列に何らかの文字列変換を組み合わせた方が使いやすいものができそうな気がする。
というよりも、この種のテンプレートすべてに言えることだが、中途半端な言語をひねくり出すとそれはタイプセーフではなくなってしまう。