ざっと概要を言うと、ブラウザから送られてくるフォームのデータを、どうやってScalaの値にマッピングするのか、ということです。
マッピングの前に、どうやって値を取得するんだ?という疑問もありますが、あまり深く考えずに取得できるようになっています。
言い方を変えると、マッピングする部分を書けば、後はPlayがうまいこと値をrequestから引っ張ってきてくれるということです。
ただ、Play frameworkらしからぬ感じなのですが、具体的なサンプルが少なくて、ちょっとマニュアルが分かりづらかったです。(私の諸々のスキルが低いというのもありますが)
なので、途中でサンプルソースを載せました。(背景が紫色の部分)
例のごとく、もし私の訳が間違っていても責任は負えませんのであしからず・・・
正確なものを読みたい方は原典をどうぞ。
http://www.playframework.org/documentation/2.0.1/ScalaForms
なお、日本Playframeworkユーザー会さんの方で翻訳作業が進行中のようですので、そちらを待たれるという手もあります。
それではどうぞ!
フォームの定義
play.api.dataパッケージは、HTTPフォームのデータを扱い、バリデーションも扱うヘルパーが含まれています。
フォームを扱う最も簡単な方法は、play.api.data.Form構造を定義することです。
import play.api.data._
import play.api.data.Forms._
val loginForm = Form(
tuple(
"email" -> text,
"password" -> text
)
)
import play.api.data.Forms._
val loginForm = Form(
tuple(
"email" -> text,
"password" -> text
)
)
以下のフォームは、結果としてMap[String,String]から、(String, String)の値を生成します。
val anyData = Map("email" -> "bob@gmail.com", "password" -> "secret")
val (user, password) = loginForm.bind(anyData).get
val (user, password) = loginForm.bind(anyData).get
もし、requestスコープの中のソースをコーディングしている場合であれば、直接requestの中身をバインドすることが出来ます。
val (user, password) = loginForm.bindFromRequest.get
複雑なオブジェクトの構築
formは値を構築・分解する機能を使用することが出来ます。
例えば、既存のケースクラスにラップすることが出来ます。
import play.api.data._
import play.api.data.Forms._
case class User(name: String, age: Int)
val userForm = Form(
mapping(
"name" -> text,
"age" -> number
)(User.apply)(User.unapply)
)
val anyData = Map("name" -> "bob", "age" -> "18")
val user: User = userForm.bind(anyData).get
import play.api.data.Forms._
case class User(name: String, age: Int)
val userForm = Form(
mapping(
"name" -> text,
"age" -> number
)(User.apply)(User.unapply)
)
val anyData = Map("name" -> "bob", "age" -> "18")
val user: User = userForm.bind(anyData).get
Note:tupleとmappingを使用することによる違いは、tupleを使用する場合、constructionとdeconstructionを定義する必要が無いということです。
(だって私たちはtupleの構築と分解方法を知っていますよね?)
mapping関数は、あなたに、あなたのカスタマイズされた関数を定義させます。
ケースクラスを構築したり分解したりしたい場合はデフォルトのapplyメソッドとunapplyメソッドを使用するだけです。
(だって私たちはtupleの構築と分解方法を知っていますよね?)
mapping関数は、あなたに、あなたのカスタマイズされた関数を定義させます。
ケースクラスを構築したり分解したりしたい場合はデフォルトのapplyメソッドとunapplyメソッドを使用するだけです。
※注意(公式にない私的メモ)
ここの訳は良く分からない。
construction and deconstruction functionsをいったいなんと訳せばいいのか・・・
ここの訳は良く分からない。
construction and deconstruction functionsをいったいなんと訳せばいいのか・・・
もちろん、Formシグネチャがケースクラスに正確に一致しないことも良くあります。
例として、利用規約に同意するために使われるチェックボックスを含んだformを使ってみましょう。
Userに値を与える必要はありません。
これはフォームのバリデーションに一度使用されますが、役に立つ情報を教えてくれないダミーのフィールドです。
私たちは自分自身の構築・分解のための機能を定義できるので、これを扱うのはとても簡単です。
val userForm = Form(
mapping(
"name" -> text,
"age" -> number,
"accept" -> checked("Please accept the terms and conditions")
)((name, age, _) => User(name, age))
((user: User) => Some((user.name, user.age, false))
)
mapping(
"name" -> text,
"age" -> number,
"accept" -> checked("Please accept the terms and conditions")
)((name, age, _) => User(name, age))
((user: User) => Some((user.name, user.age, false))
)
Note:formに既存のUserを代入したとき、deconstructionが使用されています。
これは、Userをデータベースからロードして、それを更新するためにフォームを用意する場合に便利です。
これは、Userをデータベースからロードして、それを更新するためにフォームを用意する場合に便利です。
制約の定義
それぞれのmappingで、バインディングの過程で値をチェックする、追加のバリデーションの制約を定義することが出来ます。
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.Constraints._
case class User(name: String, age: Int)
val userForm = Form(
mapping(
"name" -> text.verifying(required),
"age" -> number.verifying(min(0), max(100))
)(User.apply)(User.unapply)
)
import play.api.data.Forms._
import play.api.data.validation.Constraints._
case class User(name: String, age: Int)
val userForm = Form(
mapping(
"name" -> text.verifying(required),
"age" -> number.verifying(min(0), max(100))
)(User.apply)(User.unapply)
)
Note:こういう書き方もできます
このconstructsは上の例と全く同じ制約になります。
mapping(
"name" -> nonEmptyText,
"age" -> number(min=0, max=100)
)
"name" -> nonEmptyText,
"age" -> number(min=0, max=100)
)
このconstructsは上の例と全く同じ制約になります。
また、フィールドにアドホックな制約を定義することもできます。
val loginForm = Form(
tuple(
"email" -> nonEmptyText,
"password" -> text
) verifying("Invalid user name or password", fields => fields match {
case (e, p) => User.authenticate(e,p).isDefined
})
)
tuple(
"email" -> nonEmptyText,
"password" -> text
) verifying("Invalid user name or password", fields => fields match {
case (e, p) => User.authenticate(e,p).isDefined
})
)
バインディングのエラーを制御する
制約を定義した場合には、バインディング時のエラーを制御できるようにする必要があります。
以下のように、foldを使うことが出来ます。
loginForm.bindFromRequest.fold(
formWithErrors => // binding failure, you retrieve the form containing errors,
value => // binding success, you get the actual value
)
formWithErrors => // binding failure, you retrieve the form containing errors,
value => // binding success, you get the actual value
)
※注意(公式にない私的メモ)
実際のサンプルはこんな感じ
実際のサンプルはこんな感じ
def debug = Action { implicit request =>
  // ************************************************************
  // フォームの定義
  // ************************************************************
  // nonEmptyTextやnumberといったプロパティ(とメソッド)はplay.api.data.Formsクラスの中
  val myForm = Form(
  tuple(
  "val1" -> nonEmptyText,
  "val2" -> number
  )
  )
 
 
  // ************************************************************
  // request(POSTされたデータ)を取得する(2パターン)
  // ************************************************************
  /**
  * 最もシンプルなフォームの値の取得方法
  * フォームの定義で 制約(constraints)を指定している値で、実際のフォームに値が
  * 入力されていない場合、この取得方法だと以下のエラーが発生する
  * [NoSuchElementException: None.get]
  * フォームの定義で制約を付けている場合は、必ずfoldを使ってエラーハンドリングをすること!
  * 値が入力されている場合は正常に動いてしまうので、正常系のテストでは問題なく
  * 動作してしまう為、注意すること。
  */
  val(val1, val2) = myForm.bindFromRequest.get
  Ok("bindFormRequest.get" + " -> " + (val1 + 1) + " : " + (val2 + 1))
 
 
  /**
  * フォームの定義に制約を指定している場合は、foldを使って必ずエラーハンドリングするように!
  * 問題ない場合はタプルとして値が取得でき、型の制約も特に問題なく動作する
  */
  myForm.bindFromRequest.fold(
  errors => Ok("NOOOOO"),
  value => Ok("bindFormRequest.fold" + " -> " + (value._1 + 1) + " : " + (value._2 + 1))
  )
}
  // ************************************************************
  // フォームの定義
  // ************************************************************
  // nonEmptyTextやnumberといったプロパティ(とメソッド)はplay.api.data.Formsクラスの中
  val myForm = Form(
  tuple(
  "val1" -> nonEmptyText,
  "val2" -> number
  )
  )
 
 
  // ************************************************************
  // request(POSTされたデータ)を取得する(2パターン)
  // ************************************************************
  /**
  * 最もシンプルなフォームの値の取得方法
  * フォームの定義で 制約(constraints)を指定している値で、実際のフォームに値が
  * 入力されていない場合、この取得方法だと以下のエラーが発生する
  * [NoSuchElementException: None.get]
  * フォームの定義で制約を付けている場合は、必ずfoldを使ってエラーハンドリングをすること!
  * 値が入力されている場合は正常に動いてしまうので、正常系のテストでは問題なく
  * 動作してしまう為、注意すること。
  */
  val(val1, val2) = myForm.bindFromRequest.get
  Ok("bindFormRequest.get" + " -> " + (val1 + 1) + " : " + (val2 + 1))
 
 
  /**
  * フォームの定義に制約を指定している場合は、foldを使って必ずエラーハンドリングするように!
  * 問題ない場合はタプルとして値が取得でき、型の制約も特に問題なく動作する
  */
  myForm.bindFromRequest.fold(
  errors => Ok("NOOOOO"),
  value => Ok("bindFormRequest.fold" + " -> " + (value._1 + 1) + " : " + (value._2 + 1))
  )
}
formをデフォルト値で初期化する
時々データを編集するために、formに既存のデータを代入したいこともあるでしょう。その場合は以下のようにします。
val filledForm = userForm.fill(User("Bob", 18))
ネストされた値
formのmappingでは、ネストされた値を定義することもできます。
case class User(name: String, address: Address)
case class Address(street: String, city: String)
val userForm = Form(
mapping(
"name" -> text,
"address" -> mapping(
"street" -> text,
"city" -> text
)(Address.apply)(Address.unapply)
)(User.apply, User.unapply)
)
case class Address(street: String, city: String)
val userForm = Form(
mapping(
"name" -> text,
"address" -> mapping(
"street" -> text,
"city" -> text
)(Address.apply)(Address.unapply)
)(User.apply, User.unapply)
)
この方法でネストされたデータを使用する場合、ブラウザから送られるフォームの値は「address.street, address.city, etc」の様な命名にしなければなりません。
繰り返される値
formのmappingでは、繰り返される値(同名の複数の値)を定義することもできます。
case class User(name: String, emails: List[String])
val userForm = Form(
mapping(
"name" -> text,
"emails" -> list(text)
)(User.apply, User.unapply)
)
val userForm = Form(
mapping(
"name" -> text,
"emails" -> list(text)
)(User.apply, User.unapply)
)
このような繰り返される値を使用する場合、ブラウザから送られるフォームの値は「emails[0], emails[1], emails[2], etc」のような命名にしなければなりません。
Optionな値
formのmappingでは、Optionな値を定義することもできます。
case class User(name: String, email: Option[String])
val userForm = Form(
mapping(
"name" -> text,
"email" -> optional(text)
)(User.apply, User.unapply)
)
val userForm = Form(
mapping(
"name" -> text,
"email" -> optional(text)
)(User.apply, User.unapply)
)
Note:emailフィールドは、requestの中に存在しない場合と、ブランク値が入っている場合には、Noneがセットされて無視されます。
無視する値
送られてきた値を無視して、フィールドに静的な値を持たせたい場合は以下のようにします。
case class User(id: Long, name: String, email: Option[String])
val userForm = Form(
mapping(
"id" -> ignored(1234),
"name" -> text,
"email" -> optional(text)
)(User.apply, User.unapply)
)
val userForm = Form(
mapping(
"id" -> ignored(1234),
"name" -> text,
"email" -> optional(text)
)(User.apply, User.unapply)
)
あなたは今、Optionな値、ネストされた値、同名の複数の値を混在させて、複雑なformを作成することが出来ます。
以上で終了です。
0 件のコメント:
コメントを投稿