astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

Play Framework 2.5でPostリクエストを処理してみる

https://www.playframework.com/assets/images/logos/play_full_color.png

アスタミューゼ 開発部のYanagitaです。

今回は前回執筆したPlay Frameworkの続きで、 Play Frameworkを使用してPost送信したデータを受け取るところを書きます。

事前準備

Play Frameworkの実行環境の設定がまだの方はこちらを参照

Postデータを受け取って表示する

まず、リクエストで受け取ったデータを格納するためのFormクラスとそのFormクラスにリクエストデータを格納するためのルールを記述します。

格納するためのFormクラス
/sample-app/app/forms/RequestForm.scala

case class RequestForm(
                      name: String, // 名前
                      sex: String, // 性別
                      birthday: Date, // 誕生日
                      height: Int, // 身長
                      bloodType: String // 血液型
                    )

リクエストデータを上のFormクラスに格納するルール
今回はサンプルのためControllerクラス内にルールを記述します。
他のリクエスト(Controllerクラス)でも使用する場合はルールを切り出してください。

/sample-app/app/controllers/SampleController.scala

package controllers

import javax.inject.Inject

import forms.RequestForm
import play.api.data.Form
import play.api.data.Forms._
import play.api.mvc._
import play.api.i18n.Messages.Implicits._
import play.api.Play._

class SampleController @Inject() extends Controller {
 // ここからが格納ルール
 val form = Form(
   mapping(
     "name" -> text,
     "sex" -> text,
     "birthday" -> date,
     "height" -> number,
     "bloodType" -> text
   )(RequestForm.apply)(RequestForm.unapply)
 )
 // ここまでが格納ルール
}

格納ルールの定義は、リクエストのパラメータ名とplay.api.data.Forms配下の型をマッピングしていきます。
以下はマッピングの組わせ

*Form内の型 *マッピング
scala.String play.api.data.Forms.text
scala.Int play.api.data.Forms.number
scala.Long play.api.data.Forms.longNumber
java.util.Date play.api.data.Forms.date
java.sql.Date play.api.data.Forms.sqlDate
org.joda.time.DateTime play.api.data.Forms.jodaDate
org.joda.time.LocalDate play.api.data.Forms.jodaLocalDate
scala.String play.api.data.Forms.email
scala.Boolean play.api.data.Forms.boolean
scala.Boolean play.api.data.Forms.checked
scala.Option play.api.data.Forms.optional

次に、入力ページと結果ページを表示するActionを上のControllerクラスに追記します。
前回はOkメソッドでただの文字列を返却するだけでしたが、今回はHTMLを返却したいのでHTMLを記述するテンプレートを指定します。
指定の際、テンプレート内で動的に変わるデータを扱う場合はテンプレートの引数に設定してます。
今回はリクエストデータをそのまま表示するため、Formクラスを設定します。
/sample-app/app/controllers/SampleController.scala

  // 入力ページを表示するAction
  def input = Action {
    Ok(views.html.sample.input(form))
  }
  // 結果ページを表示するAction
  def result = Action {
    Ok(views.html.sample.result(form))
  }

※ この時点ではテンプレートが未作成のためIDEだとエラーとなります。

次に、テンプレートを作成します。
テンプレートは"/プロジェクトディレクトリ/app/views"配下に配置し、拡張子は".scala.html"となります。
入力ページのテンプレート
/sample-app/app/views/sample/input.scala.html

@import helper._
@import forms.RequestForm
@(requestForm: Form[RequestForm])(implicit messages: Messages)
<!DOCTYPE html>
<html>
   <head>
       <title>Sample page.</title>
   </head>
   <body>
       <h2>Sample page(input).</h2>
       <hr>
       Sample form.
       @form(action = routes.SampleController.result()) {
           <!-- name -->
           @inputText(requestForm("name"))
           <!-- sex -->
           @inputRadioGroup(
               requestForm("sex"),
               options = Seq("M"->"Male","F"->"Female"))
           <!--  birthday -->
           @inputDate(field = requestForm("birthday"))
           <!-- height -->
           @inputText(requestForm("height"))
           <!-- bloodType -->
           @select(
               field = requestForm("bloodType"),
               options = Seq(
                   "A" -> "A",
                   "B" -> "B",
                   "O" -> "O",
                   "AB" -> "AB"
               )
           )
           <input type="submit" >
       }
   </body>
</html>

結果ページのテンプレート
/sample-app/app/views/sample/result.scala.html

@import forms.RequestForm
@(requestForm: Form[RequestForm])(implicit messages: Messages)
<!DOCTYPE html>
<html>
    <head>
        <title>Sample page.</title>
    </head>
    <body>
        <h2>Sample page(result).</h2>
        <hr>
        result <br>
        <table border="1">
            <tr>
                <th>name</th><td>@(requestForm("name").value)</td>
            </tr>
            <tr>
                <th>sex</th><td>@(requestForm("sex").value)</td>
            </tr>
            <tr>
                <th>birthday</th><td>@(requestForm("birthday").value)</td>
            </tr>
            <tr>
                <th>height</th><td>@(requestForm("height").value)</td>
            </tr>
            <tr>
                <th>bloodType</th><td>@(requestForm("bloodType").value)</td>
            </tr>
        </table>
    </body>
</html>

記述の内容は、JSPのPlay Framework版のようなもので、 表示に関するところはJSP同様にHTMLタグを使用して記述し、JSPのディレクティブやスクリプトレットに当たるものを"@〜〜"で記述していきます。
※ Play Frameworkのテンプレートでは、変数を参照したり、ロジックを書く場合は「@」を先頭についけるというルールがあります。
もう少し解説を入れると、

  • 入力ページの1行目〜2行目は、テンプレート内で必要なクラスをインポートしています。
    これは、JSPで言うところのpageタグのimportと同じ役割と果たしていて、Play Frameworkでは「@import クラスパス」で指定します。
  • 入力ページの3行目、メソッド作成時に引数を指定するのと一緒で、テンプレート内で使用するデータを引数として指定します。
    今回、Actionでテンプレートに値を渡す記述を行ったので、テンプレートではplay.api.data.Form[格納するためのFormクラス]を受け取る記述をしています。

また少し特殊なところで、Play FrameworkにはForm template helpersと呼ばれる機能がビルドインされています。
Form template helpersとは、テンプレート内でフォームや入力フィールドを自動生成してくれる機能で、 HTMLタグの自動生成のほか、エラーメッセージの出力などもしてれます。
今回は、Form template helpersの機能を使用して、Formタグ(@form)、テキストボックス(@inputText、@inputDate)、ラジオボタン(@inputRadioGroup)、セレクトボックス(@select)を表示するサンプルにしています。
サンプルで使用した以外には、

  • パスワード ・・・ @inputPassword
  • ファイル ・・・ @inputFile
  • テキストエリア ・・・ @textarea
  • チェックボックス ・・・ @checkbox

があります。
Form template helpersの細かい設定は別で執筆を予定しているのでここではこれ以上深くは触れません。

ちなみに、テンプレートファイルは一度Scalaコードに変換された後、classファイルとなります。
そのため、Actionから指定する場合は、テンプレートを配置したはパッケージとロジック上のパッケージが異なるので注意が必要です。

テンプレートファイルのパス → views/sample/input.scala.html
Action上で指定する場合 → views.html.sample.input
※ viewsパッケージの下にhtmlパッケージを入れます。

Formクラス、ページを表示するAction、ページのテンプレートができたところで、 routeファイルにURIとActionのひも付けを追記しブラウザで確認します。
/sample-app/conf/routes

# 入力ページを表示する
GET     /sample/input               controllers.SampleController.input
# 結果ページを表示する
POST  /sample/input                controllers.SampleController.result

ブラウザに表示される入力ページ
f:id:astamuse:20160823212323p:plain:h566

もしエラーが発生する場合は、テンプレートファイルの変換が終わっていない可能性があります。
一度、cleanを実施して、再度compileを実行してください。

activator clean
activator compile

当然ですが、ここまででは結果画面に移れても入力した内容は表示されません。
結果ページを表示するActionでリクエストをFormクラスにバインドする処理が抜けているからです。
最後にリクエストのデータをFormクラスにバインドする処理を追記します。
Formクラスへのバインド方法は、FormクラスのbindFromRequest#foldメソッドを使用し、 バインド結果毎に処理を記述します。
/sample-app/app/controllers/SampleController.scala

  // 結果ページを表示するAction
  def result = Action { implicit request => // リクエストオブジェクトを宣言
    form.bindFromRequest().fold(
      errorForm => { // バインドエラー = 入力エラーが発生した場合
        Ok(views.html.sample.input(errorForm)) // 入力画面を再表示します。
      },
      requestForm => { // バインド成功 = 入力エラーがない場合
        Ok(views.html.sample.result(form.fill(requestForm))) // 結果画面を表示します。
      }
    )
  }

前述の通り、バインド結果で実行される処理が別れます。
バインドに失敗した(入力エラーが発生した)場合は、一つ目の引数で渡した処理が実行され、 バインドに成功した(入力エラーが発生しない)場合は、二つ目の引数で渡した処理が実行されます。 この時、それぞれの処理で渡ってくる変数の型が異なるので注意が必要です。
バインドに失敗した場合は、 play.api.data.Form[格納するためのFormクラス]になります。これはエラーメッセージを含むために play.api.data.Formクラスでラップされています。 バインドに成功した場合は、格納するためのFormクラスとなります。

ブラウザに表示される結果ページ
f:id:astamuse:20160823234850p:plain:h336

Play FrameworkでのPost処理はいかがだったでしょうか?
コード量も少なく簡単だったと思います。
次回は初期値の設定と入力チェックについて執筆したいと思います。

最後に

アスタミューゼでは現在、エンジニア・デザイナーを募集中です。 興味のある方はぜひ 採用サイト からご応募ください。

Copyright © astamuse company, ltd. all rights reserved.