Scala-Tools のサービスが終了した件

sbt で標準で参照しているリポジトリ、scala-tools のサービスが先日終了したようです。しばらく移行期間として移行先のリポジトリにリダイレクトをしていたのですが、それも終了したとのこと(たまたま今落ちてるだけかもしれないとのこと。でも将来なくなるのは確か)。

そこで scala-tools を参照しているビルドツールは設定変更を余儀なくされています。とりあえず、Play 2.0 は、project/Build.scala を以下のように修正すれば良いようです。

import sbt._
import Keys._
import PlayProject._

object ApplicationBuild extends Build {

  val appName = "myapp"
  val appVersion = "1.0-SNAPSHOT"

  val appDependencies = Seq(
    // Add your project dependencies here,
  )

  val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
    // Add your own project settings here
    // 以下のように移転先リポジトリを指定
    resolvers ++= Seq(
      "sonatype-releases" at "https://oss.sonatype.org/content/repositories/releases/",
    ),
    // 以下のようにこれまでの Scala-Tools リポジトリを見ないようにする
    externalResolvers ~= (_.filter(_.name != "Scala-Tools Maven2 Repository"))
  )

}

Heroku で Play! 製アプリケーションが文字化けしてお困りの方に

ローカルでは上手く行っているのに Heroku にデプロイすると上手くいかないという方に朗報! 先日 Heroku が提供する Scala の BuildPack が更新されまして、これを使用することで文字化けしないようになります。

Heroku にデプロイすると文字化けすると困っているプロジェクト下で以下のコマンドを実行してみてください。

heroku config:add BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-scala.git

これで以降のビルドから文字化けしなくなります。

MongoURI がいい加減すぎワロエナイ

Heroku + MongoLab 環境に Casbah を使って MongoDB に接続する Play 2.0 で作成したアプリケーションをデプロイしようとしました。

それで Heroku 上で MONGOLAB_URI の設定値として提供される MongoDB の接続 URI を com.mongodb.casbah.MongoURI(com.mongodb.MongoURI)に食わせたらまったくもってでたらめなパースをするので自分でなんとかするコードを書きました。使い方は以下のコード内のコメント参照。MongoDB の接続 URI は conf/application.conf 内で mongodb.default.uri で設定するか、起動時に -D オプションで与えるようにしてください。

package mongodb

import play.api._
import Play.current
import com.mongodb.casbah.Imports._

/**
 * MongoDB をサクッと使うためのオブジェクトです。
 */
object Mongo {
  /**
   * MongoDB 接続用 URI をパースするための正規表現
   */
  val uriPattern = "mongodb://(?:([^:^@]+)(?::([^@]+))?@)?([^:^/]+)(?::(\\d+))?/(.+)".r

  /**
   * MongoDB に接続されたブロックを提供します。
   * 接続 URI は mongodb.default.uti の設定値を使用します。
   *
   * 使い方の例
   * {{{
   *   MongoDB.withDB { db =>
   *     db("employee").findAll().toSeq
   *   }
   * }}}
   */
  def withDB[A](f: MongoDB => A): A = withDB("default")(f)

  /**
   * MongoDB に接続されたブロックを提供します。
   *
   * 使い方の例
   * {{{
   *   MongoDB.withDB("mydb") { db =>
   *     db("employee").findAll().toSeq
   *   }
   * }}}
   *
   * @param name 設定名
   */
  def withDB[A](name: String)(f: MongoDB => A): A = {
    // Play を使わない場合は↓このあたりを変えるといい
    val uri = Play.configuration.getString("mongodb." + name + ".uri").getOrElse("mongodb://localhost/SSO")

    // URI のパース
    val (usernameOpt, passwordOpt, host, portOpt, database) = parseURI(uri)

    // MongoDB に接続
    val conn = portOpt map { port =>
      MongoConnection(host, port.toInt)
    } getOrElse {
      MongoConnection(host)
    }

    try {
      // DB 取得
      val db = conn(database)
      // 必要とあれば認証処理を行う
      (usernameOpt, passwordOpt) match {
        case (Some(username), Some(password)) => db.authenticate(username, password)
        case _ =>
      }
      // 引数で指定された処理を実行
      f(db)
    } finally {
      conn.close()
    }
  }

  /**
   * MongoDB 接続 URI をパースします。
   */
  def parseURI(uri: String) = {
    uri match {
      case uriPattern(username, password, host, port, database) =>
        (Option(username), Option(password), host, Option(port), database)
    }
  }
}

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 と同じです。