Scala
PlayFramework

ScalaのPlayFrameworkの公式ドキュメントをまとめながら理解を深めていく【Form編】

More than 1 year has passed since last update.

現在ScalaでPlayFrameworkについて学習をしているのですが、結構仕様変更が激しいフレームワークのようで公式ドキュメントに頼らざるを得ないケースが多いです。
そこで今回は自身の技術習得と共に、自分と同様にScalaやPlayFrameworkを扱い始めたが苦戦している方に少しでも役に立てばと考え、公式ドキュメントを翻訳しつつ、要点についてざっくりまとめて行こうと思います。

前提

私自身ScalaもPlayFrameworkを最近触り始めたばかりです。ついでに英語も苦手です。もちろん間違った情報を提供しないように注意を払いつつ記述させてはいただきますが、もし誤った認識や内容を見かけた際は優しくご指摘いただけると非常に助かります。

バージョン情報

Scala: 2.12.4
PlayFramework: 2.6.7(project/plugins.sbtから確認)
sbt: 1.0.3

参照したURL

PlayFramework公式
https://www.playframework.com/documentation/2.6.x/ScalaForms

Formに関するまとめ

概要

PlayFrameworkではPOSTリクエストでデータが送られてきたとき、そのデータをFormオブジェクトを利用して扱います。

下準備

Formを利用する場合、クラスに以下のimportを記載しましょう。Formから送られてくるデータを扱うのは基本的にコントローラーとなりますので、コントローラーファイルに記載することがほとんどでしょう。

import play.api.data._
import play.api.data.Forms._

Formを定義する

まずは作成したいフォームの要素を含んだcase classを定義します。
名前と年齢を入力させるフォームを作成したい場合は以下のように定義します。

case class UserData(name: String, age: Int)

次にこのcase classに転送されてきたデータをぶち込むためのForm構造を作成します。

val userForm = Form(
  mapping(
    "name" -> text,
    "age" -> number
  )(UserData.apply)(UserData.unapply)
)

バリデーション/入力規則

割愛。大人になったら書きます。

ViewでのFormの表示

Formを定義したら、そのFormを画面に表示させていきます。
現状PlayではテンプレートエンジンとしてTwirlが利用されております。詳細についてはまたの機会に紹介できればと思いますが、ひとまずは魔法の@で色々してくれる憎い奴と覚えておけば良さそうです。
まずはviewファイルの先頭に以下のように記述します。

@(userForm: Form[UserData])(implicit messages: Messages)

このViewを表示する際にコントローラーから渡されている引数というイメージと捉えています。Messageについてはバリデーションエラー等の受け渡しに利用しますが、まだ詳細が読み込めていないため恒例のスキップとさせていただきます。
この宣言は新規登録画面のような空のフォームを表示する場合にも必要となってきますので注意しておきましょう。
Viewを呼ぶ時のコントローラー側のアクションは以下のように定義します。

def index = Action { implicit request =>
  Ok(views.html.user(userForm))
}

userFormなんてねーぞ!と怒られた時はUserControllerクラスに

import UserController._

を記述しておきましょう。あるいはUserController.userFormを渡してもOKです。Scalaのクラスとオブジェクトに関する知識がまだグダグダなので詳細な説明はできません。すみません。

余談ですが普段はRailsを使用しているのでnewアクションを作成しようとしたら、エラーが発生しました。インスタンスを生成するnewとぶつかっているのでしょうか。真相は分かりません。
で、このコントローラーからuserFormが受け渡されたことで以下のようにFormタグを記述できるようになります。

@helper.form(action = routes.Application.userPost()) {
  @helper.inputText(userForm("name"))
  @helper.inputText(userForm("age"))
}

この辺りは割と直感的に理解しやすいと思います。userPostアクションへデータを渡すためのnameとageに対応するForm要素を作ってくれています。
@helperを毎回打つのがめんどくさい場合はViewファイルの先頭に

@import helper._

と宣言しておけば

@form(action = routes.Application.userPost()) {
  @inputText(userForm("name"))
  @inputText(userForm("age"))
}

と記述できます。
このhelperが提供してくれる一般的な機能は以下の通り。

名称 機能
form form要素の描画
inputText テキスト入力フォームの描画
inputPassword パスワード入力フォームの描画
inputDate 日付入力フォームの描画
inputFile ファイルアップロードフォームの描画
inputRadioGroup ラジオボタンの描画
select セレクトボックスの描画
textarea テキストエリアの描画
checkbox チェックボックスの描画

などなどです。
公式ドキュメントのFormのページではsubmitに関する記述がされいませんが、formのぶろっくないにHTMLタグで記述すれば良さそうです。

@form(action = routes.Application.userPost()) {
  @inputText(userForm("name"))
  @inputText(userForm("age"))
  <input type="submit" name="" value="保存">
}

これでFormの値がPOST送信されるようになりましたのでuserPostで実際にその値を取り出すところまでやってみましょう。
リクエストからフォームで渡されたデータを取り出すには、Formオブジェクトの持つbindFromRequestというメソッドを利用します。
コントローラー内で以下のように記載することで、フォームの入力情報を取得することが可能です。

val userData = userForm.bindFromRequest.get

ただしこれだけだと、バリデーションを設定してエラーが発生した時などに例外が発生してしまいます。そういった場合のため、公式ドキュメントではfoldメソッドを使用して以下のような取得の仕方をすることが推奨されています。

userForm.bindFromRequest.fold(
  formWithErrors => {
    // binding failure, you retrieve the form containing errors:
    BadRequest(views.html.user(formWithErrors))
  },
  userData => {
    /* binding success, you get the actual value. */
    val newUser = models.User(userData.name, userData.age)
    val id = models.User.create(newUser)
    Redirect(routes.Application.home(id))
  }
)

リクエストからフォーム入力情報が取得できたときは、Userテーブルにデータを登録し、入力情報の取得に失敗したときはBadRequestを利用して、エラーページ情報と共に元のページに戻るように命令をしています。
取得成功時の処理についてはDB操作のパッケージによって変わってくると思いますので、詳細はまたの機会に。

まとめ

ひとまず今回はこんな感じで終了です。僕の知識不足から省略多めで申し訳ない気持ちでいっぱいですが、少しでもどなたかのお役に立てば嬉しく思います。
誤解を招きそうな記述であったり、認識に誤りを見つけた際はご指摘いただけますと幸いです。
閲覧いただき、ありがとうございました。