Locked History Actions

Play/StepByStep

ステップバイステップ

以下の記述は、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とするとエラーが発生してしまう。 トラブルと解消を参照のこと。

ルーティングの確認

conf/routesを確認すると、

GET     /                                       Application.index

という記述がある。 これは、ルートにアクセスされた場合には、controllersパッケージのApplicationオブジェクトのindexメソッドを呼び出すという意味。 ここで記述する、オブジェクト名はcontrollersパッケージに存在することを前提としている。これはJavaの命名規約に背くものであるが、このアプリ内だけで使うものなので問題ないだろう、と考えたものと思われる。 controllers以外のパッケージのものを使う場合は、パッケージ名を明示する必要がある。

routesの記述はJava版と同じと考えられる。 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("<html><body><h1>hello, wold</h1></body></html>")
    }
}

まず、「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(<html><body><h1>{name + " --- " + address}</h1></body></html>)
    }

とすると、

http://localhost:9000/?name=sugimura&address=tokyo

というURLの呼び出しで

sugimura --- tokyo

と表示される。しかしこのやり方では、「プログラム上でパラメータ名を生成する」ことができなくなる。この場合には、以下のように記述する。

      def index() = {
        val name = params.get("name")
        val address = params.get("address")
        Html(<html><body><h1>{name + " --- " + address}</h1></body></html>)
    }

このあたりもJava,Scalaで共通の模様。 http://playdocja.appspot.com/documentation/1.2/controllers#params

Scala版特有のパラメータハンドリング

おそらくJava版には無い、Scala版特有のものとして便利な処理方法がある。例えば以下のように記述できる。

    def index(name: Option[String], address: String = "Japan") = {
        Html(<html><body><h1>{name.getOrElse("gonbei") + " --- " + address}</h1></body></html>)
    }

あるいは、次のような書き方もある。

  case class User(name: String, address: String)
  def index(user: User) = {
    Html(<html><body><h1>{user.toString()}</h1></body></html>)
  }

が、パラメータは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(
      "<html><body><a href='" +
        Router.reverse("Application.login").url
      + "'>to login page</a></body></html>"
    )
  }
  def login() = {
    Html(<html><body>enter id and password...</body></html>)
  }
}

つまり、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が呼び出せている。こういう事情はきちんと書いておくべきかと思うのだが。。。。

テンプレートの書き方

さて、テンプレートの書き方は以下で説明されている。

要するに@の後に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というメソッドが何らかのリスト表示メソッドであれば、

<a href="@action(controllers.Application.list)">リスト表示</a>

※もちろん、routesにも記述がなければならない。

assetは

<img src='@asset("/public/images/sample.jpg")'>

などとする。これももちろんroutesに適切な記述がなければいけない。

エスケープに関するバグ

デフォルトで文字列のhtmlエスケープがされるが、play-scala 0.9.1ではエスケープ処理にバグがある。 また、エスケープさせないための記述は少々面倒。

http://ruimo.com/2011/09/01/1314830640000.htmlを参照のこと

評価

テンプレート内で別の言語ではなく、scalaがそのまま使えるというのは多いに評価できる点であるが、しかし先に述べたように、@以降のScalaコードの有効範囲のルールがわかりづらい。いくつか記述してみないと感覚がつかめないかもしれない。

また、この方式のテンプレートは、Wicket等のものとは異なり、「htmlのカタチ」はしていないので、デザイナとの共同作業は不向きであると思われる。あくまでも技術者向けではないだろうか。しかし、そうであるなら、個人的には、scalaの生文字列に何らかの文字列変換を組み合わせた方が使いやすいものができそうな気がする。

というよりも、この種のテンプレートすべてに言えることだが、中途半端な言語をひねくり出すとそれはタイプセーフではなくなってしまう。