リクエストパラメータにIDが指定押されているかどうかに応じて以下の処理を行います。

  • リクエストパラメータにIDなし ⇒ 新規登録画面を表示します。
  • リクエストパラメータにIDあり ⇒ USERSテーブルを検索し、該当のユーザ情報を初期表示した編集画面を表示します。

フォーム

画面からの入力値を受け取るためのFormを定義します。Formは必ずしもコントローラに定義する必要はないのですが、コントローラでの処理に強く依存するため特に理由がない限りコントローラクラスのコンパニオンオブジェクトに定義するとよいでしょう。

ここではUserControllerと同じソースファイルに以下のようなコンパニオンオブジェクトを追加します。

object UserController {
  // フォームの値を格納するケースクラス
  case class UserForm(id: Option[Long], name: String, companyId: Option[Int])

  // formから送信されたデータ ⇔ ケースクラスの変換を行う
  val userForm = Form(
    mapping(
      "id"        -> optional(longNumber),
      "name"      -> nonEmptyText(maxLength = 20),
      "companyId" -> optional(number)
    )(UserForm.apply)(UserForm.unapply)
  )
}

コンパニオンオブジェクトとは、クラスやトレイトと同じファイル内に同じ名前で定義されたオブジェクトのことで、コンパニオンオブジェクトと対応するクラスやトレイトは互いにprivateなメンバーにアクセスできるなどの特徴があります。クラスやトレイトで使用する共通的なメソッドやクラス等を括り出したりするのに使います。

POINT

  • コンパニオンオブジェクトとは、クラスと同じファイル内に同じ名前で定義されたオブジェクトのことです
  • FormはStrutsのアクションフォームのようなものです
  • マッピングに従ってバリデーション(後述)が行われます

ビュー

続いてviews.userパッケージにedit.scala.htmlを実装します。引数にはFormのインスタンスと、プルダウンで選択する会社情報を格納したSeqを受け取ります。

@(userForm: Form[controllers.UserController.UserForm], companies: Seq[models.Tables.CompaniesRow])(implicit messages: Messages)

@* テンプレートで利用可能なヘルパーをインポート *@
@import helper._

@main("ユーザ作成") {

  @* IDがある場合は更新処理、ない場合は登録処理を呼ぶ *@
  @form(userForm("id").value.map(x => routes.UserController.update).getOrElse(routes.UserController.create), 'class -> "container", 'role -> "form") {
    <fieldset>
      <div class="form-group">
        @inputText(userForm("name"), '_label -> "名前")
      </div>
      <div class="form-group">
        @select(userForm("companyId"), companies.map(x => x.id.toString -> x.name).toSeq, '_label -> "会社", '_default -> "-- 会社名を選択してください --")
      </div>
      @* IDがある場合(更新の場合)のみhiddenを出力する *@
      @userForm("id").value.map { value =>
        <input type="hidden" name="id" value="@value" />
      }
      <div>
        <input type="submit" value="保存" class="btn btn-success">
      </div>
    </fieldset>
  }

}

テンプレート一行目に(implicit messages: Messages)という引数が定義されていますが、これはテンプレート中で使用しているinputTextなどのヘルパーがメッセージ等の国際化のために必要とするもので、コントローラにDIしたMessagesApiによって暗黙的に渡されます。このハンズオンでは特に使用しませんが、Play2アプリケーションの国際化対応に必要になるものですので覚えておくとよいでしょう。

POINT

  • テンプレートでも@importでインポート文を記述することができます
  • @import helper._でPlayが提供する標準ヘルパー(フォームなどを出力する関数)を使用できるようになります

コントローラ

最後にUserControllereditメソッドを実装します。引数idが指定されていた場合は空のForm、指定されていた場合はForm#fillメソッドでFormに初期表示する値をセットしたうえでテンプレートを呼び出すようにします。

// コンパニオンオブジェクトに定義したFormを参照するためにimport文を追加
import UserController._

def edit(id: Option[Long]) = Action.async { implicit rs =>
  // リクエストパラメータにIDが存在する場合
  val form = if(id.isDefined) {
    // IDからユーザ情報を1件取得
    db.run(Users.filter(t => t.id === id.get.bind).result.head).map { user =>
      // 値をフォームに詰める
      userForm.fill(UserForm(Some(user.id), user.name, user.companyId))
    }
  } else {
    // リクエストパラメータにIDが存在しない場合
    Future { userForm }
  }

  form.flatMap { form =>
    // 会社一覧を取得
    db.run(Companies.sortBy(_.id).result).map { companies =>
      Ok(views.html.user.edit(form, companies))
    }
  }
}

上記のコードでは以下の記述で会社情報の一覧を取得しています。

Companies.sortBy(_.id).result

これは以下のSQLと同じ意味になります。

SELECT * FROM COMPANIES ORDER BY ID

このアクションもAction.asyncFutureを返しますが、リクエストパラメータにIDが存在しない場合は以下のようにして自分でuserFormを返すFutureを作成している点に注意してください。

Future { userForm }

実行

ここまで実装したらブラウザで一覧画面から新規作成やユーザ名のリンクをクリックし、以下のように登録画面と編集画面が表示されることを確認します。

ユーザ登録画面

ユーザ編集画面