Tag Archives: Scala - Page 2

Play 2.0 でカスタムフォームデータマッピング

前回の記事で、「あれ? Double(Float)型使えないの? 少数とかどうするの?」と思った方もいると思います。そうでないにしても、他の型にマッピングしたいというのは当然の要望でしょう。

そこで今回は、任意の型にフォームデータをマッピングする方法を紹介します。

まず必要なのは play.api.data.format.Formatter トレイトの実装クラスです。これを提供するメソッドを作成します。作成する場所は object であればどこでもかまいませんが、どうせ controllers でしか使用しないというのであれば、controllers のパッケージオブジェクトだと何かと楽です(controllers のパッケージオブジェクトでは上手くいかないようでした)。なぜ object かというと、以下のように implicit def なので import が必要だからです。

まず、作成するメソッドに必要なクラス等を使用する import 宣言は以下のとおりです。

import play.api.data._
import Forms._
import play.api.data.format._
import Formats._

そして作成するメソッドですが、以下は Double 型へマッピングする場合の例です。

  /**
   * リクエストデータを Double 型に変換するフォーマッタを取得します。
   */
  implicit def doubleFormat = new Formatter[Double] {

    /**
     * このフォーマットについて画面に表示する際に使用するメッセージ
     */
    override val format: Option[(String, Seq[Any])] = Some("format.double", Nil)

    /**
     * リクエストデータを Double 型に変換します。
     *
     * @param key リクエストデータを取り出す際に使用するキー値
     * @param data リクエストデータ
     * @return Double 型への変換が失敗した場合フォームエラー。成功した場合変換した値
     */
    def bind(key: String, data: Map[String, String]): Either[Seq[FormError], Double] = {
      stringFormat.bind(key, data).right.flatMap { s =>
        scala.util.control.Exception.allCatch[Double]
          .either(s.toDouble) // 文字列から Double に変換する処理
          .left.map(e => Seq(FormError(key, "error.double", Nil))) // それが失敗した場合
      }
    }

    /**
     * Double 型の値から、リクエストデータ用の文字列に変換します。
     */
    def unbind(key: String, value: Double): Map[String, String] = Map(key -> value.toString)
  }

ここまでやればもう使い物になるのですが、もう一声、上記のメソッドの直下あたりで以下のようにやっておくとより格好がつきます。

  val double = of[Double]

このようにフォームデータマッピングで使用できます。

  val helthForm = Form(
    tuple(
      "tall" -> double,
      "weight" -> optional(double)))

Play 2.0 でのフォーム定義

Play 2.0 が正式リリースされました。Play 1.x が Java 用フレームワークであとから Scala モジュールを加えることで Scala でも利用できていたのに対して Play 2.0 は最初から Scala に対応しています。というよりも、Scala メインと言ってもいいでしょう。

Play 2.0 はとても使いやすい Web アプリケーションフレームワークですが、まだまだ情報が不足しています。今回は公式ドキュメントの Handling form submission の補足を行います。

どんなフォームデータマッピングが使用きるのか?

公式ドキュメントではフォームデータのマッピングは text と number についてだけ少しだけ述べているに過ぎません。ですが、デフォルトでもたくさんのマッピングが利用できます。

text

フォームデータを String 型にマッピングします。文字数を制限したい場合は以下のように書きます(省略可)。minLength で最小文字数、maxLength で最大文字数を指定します。

"username" -> text(minLength = 3, maxLength = 100)

nonEmptyText

文字数0を許容しない text です。text に minLegnth = 1 と指定した場合と同じ動きになりますが、制約メッセージを表示した場合、text の場合だと Minimum Value: 1 と表示されるのに対してこれは Required と表示されます。nonEmptyText も text 同様に文字数を制限することができます。

 number

フォームデータを Int 型にマッピングします。以下のように値の範囲を制限することもできます(省略可)。

"size" -> number(min = 10, max = 20)

longNumber

フォームデータを Long 型にマッピングします。なぜか number のような指定の仕方で範囲の制限はできません。

date

フォームデータを java.util.Date 型にマッピングします。引数でフォーマットを指定できます(省略可)。フォーマット形式は java.text.SimpleDateFormat に準じます。

"birthday" -> number("yyyyMMdd")

sqlDate

フォームデータを java.sql.Date 型にマッピングします。date 同様フォーマットを引数で指定できます。

email

フォームデータを String 型にマッピングしますが、メールアドレスとして妥当な形式でなければなりません。といっても、ものすごく厳密にメールアドレスの形式を検証しているわけではありません(必要十分なレベル)。

boolean

フォームデータを Boolean 型にマッピングします。フォームデータが “true” であれば true、”false” であれば false になります。必ず小文字でなければならないようです。

checked

フォームデータを Boolean 型にマッピングします(boolean と同様の性質)が、必ず true にならなければなりません。true でない場合、引数で指定したエラーメッセージを表示します。

"accepted" -> checked("使用許諾に同意してください。")

ignored

フォームデータをマッピングしません。代わりに引数で指定したデフォルト値を設定します。これは O/R マッパ等のエンティティにフォームデータをマッピングするときにあるフィールドはフォームデータの値とひもづけない等といった場合に使用します。

"id" -> ignored(0)

optional

フォームデータを Option[A] 型にマップします。省略可能な値である場合に使用します。引数として、型パラメータ A に対応するマッピングを指定します。

"age" -> optional(number(0, 120))

list

フォームデータを List[A] 型にマップします。複数選択値を扱う際に使用します。引数として、型パラメータ A に対応するマッピングを指定します。

"favorite" -> list(text)

seq

フォームデータを Seq[A] 型にマップします。使い方は list と同じです。

Play 2.0 RC3 における i18n の勘所

Play 2.0 RC3 で i18n を行う場合、他の Web アプリケーションフレームワークに比べてちょっと違うかなと思ったところがあったのでメモ。

まず、自分のアプリケーションの conf/messages でメッセージ定義するのは多分ナシ

なぜかといいますと、Play そのもの(play_2.9.1.jar)が持っている messages のほうが優先されるので、たとえば、

contraint.required=必須

みたいなことをやっても「Required」というもともとのメッセージが表示されてしまいます。

ではどうすれば良いかといいますと、明示的にロケールを指定してやればOK。ただしこれにも癖があります。

リソースバンドルを使い慣れた Java プログラマは、ロケールが ja_JP ならば messages_ja_JP または messages_ja が読み込まれるのではないかと当たりをつけることでしょう。Github 上の Wiki によればファイル名とロケールのセパレータはアンダースコアではなくピリオドなのでロケールを指定するときは messages.ja のように書けばいいことがわかります。

ところがここがハマりポイント! ロケールが ja_JP のとき、このファイルは読み込まれません!なんと、ja と ja_JP は Play では別物なのです!

しかも、Java では ja_JP とロケールを表記するのは当たり前ですが、Play では ja-JP と書き表します。

ということで、メッセージの定義は messages.ja-JP で行うのが無難そうです。

今日の Play 2.0 SNAPSHOT

毎日追いかけている Play 2.0 SNAPSHOT。

今日はでっかく変わった所がありました。それは、evolutions の置き場です。これまでは db フォルダにありましたが、今日から conf フォルダに置くことになったようです。sample もまだ直っていないので、気をつけてください。

Play framework 2.0 SNAPSHOT 順調に仕上がってきてます

ここのところ毎日 Play framework 2.0 SNAPSHOT を github から pull してきてウォッチしています。

Beta ではできなかったテスト時の Evolutions も動くようになり、DB のテストもやりやすくなりました。

今朝試してみたら、いままで Wiki ドキュメントに載っていながらうまく動かなかった外部設定ファイルのしていもうまくいくようになっていました。

ところで将来 Heroku で使用することを考えているので、RDBMS は自ずから PostgreSQL になるのでテストでは Play framework 標準の H2 ではなく、より PostgreSQL と互換性の高い HSQLDB を使用しています(といっても、SERIAL 型が使いたいだけなのですが)。conf/application.conf には以下のように指定するといいようです。

db.default.driver=org.hsqldb.jdbc.JDBCDriver
db.default.url="jdbc:hsqldb:mem:play;sql.syntax_pgs=true"

sql.syntax_pgs=true というのがミソで、これによって PostgreSQL 風シンタックスが有効化されます。これをつけないと HSQLDB で Play framework は一切動きません。