Scala + Playのチュートリアル
はじめに
Scalaの勉強のためにPlay Frameworkを触ってみました。せっかくなので、(雑ではありますが、)チュートリアル形式でまとめてみます。
基本構文やFuture
、Either
を使ってみて、なんとなく理解することを目的としています。
参考にしたページ
勉強する上で下記を参考にしました。
環境情報
名称 | バージョン |
---|---|
sbt | 1.9.8 |
Java | adoptopenjdk-21.0.2+13.0.LTS |
Scala | 2.13.13 |
Play | 3.0.2 |
テンプレートの取得
今回はPlay Frameworkのテンプレートとして提供されているplay-scala-seed.g8
をもとにサンプルアプリケーションを作成します。
sbt new playframework/play-scala-seed.g8
上記コマンドを実行するとname
やorganization
の入力が求められますが、すべてデフォルト(何も入力せずにEnter)でもOKです。
❯ sbt new playframework/play-scala-seed.g8
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
This template generates a Play Scala project
name [play-scala-seed]:
organization [com.example]:
play_version [3.0.2]:
scala_version [2.13.13]:
sbt_giter8_scaffold_version [0.16.2]:
Template applied in {YOUR PATH}/scala-play-tutorial/./play-scala-seed
生成されたファイルをいじらずに一旦起動させてみます。
cd play-scala-seed
sbt run
起動完了後、 http://localhost:9000 にアクセスすると下記の画面が表示されます。
ユーザー参照、登録
まずは下記のステップでユーザー関連の画面を作成してみます。
-
GET /users
を実行できる - ユーザー一覧を表示できる(GETを実行してDBからデータを取得できる)
- ユーザーを登録できる
Step3完了時点の画面イメージはこんな感じです。
GET /users
を実行できる
Step1: 1つ目のステップではGET /users
のエンドポイントを新しく作ります。この段階では一旦静的なレスポンスを返却するようにします。
/users
に対応するControllerとして、下記のファイルを新規作成します。
package controllers
import javax.inject.Inject
import javax.inject.Singleton
import play.api.mvc.AbstractController
import play.api.mvc.Action
import play.api.mvc.AnyContent
import play.api.mvc.ControllerComponents
import play.api.mvc.Request
@Singleton
class UsersController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
def index = Action { implicit request =>
(Ok("OK!!"))
}
}
routes
でGET /users
とUsersController.index()
のマッピングを行います。
# Routes
# This file defines all application routes (Higher priority routes first)
# https://www.playframework.com/documentation/latest/ScalaRouting
# ~~~~
# An example controller showing a sample home page
GET / controllers.HomeController.index()
+GET /users controllers.UsersController.index()
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
動作確認
上記変更を行い、localhost:9000/users
にアクセスすると、下記の画面が表示されます。
Step2: ユーザー一覧を表示できる
このステップで行うことは下記です。
- DBでテーブル作成とデータ投入を行う
- DBからデータを取得するメソッドを作成する
- Controllerから↑のメソッドを呼び出す
- 画面にデータを表示する
今回はデータ操作ライブラリとしてSlick
、マイグレーションツールとしてEvolutions
、DBとしてH2
を利用するので、設定ファイルを変更します。
name := """play-scala-seed"""
organization := "com.example"
version := "1.0-SNAPSHOT"
lazy val root = (project in file(".")).enablePlugins(PlayScala)
scalaVersion := "2.13.13"
-libraryDependencies += guice
-libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "7.0.0" % Test
+libraryDependencies ++= Seq(
+ guice,
+ "org.scalatestplus.play" %% "scalatestplus-play" % "7.0.0" % Test,
+ "org.playframework" %% "play-slick" % "6.1.0",
+ "org.playframework" %% "play-slick-evolutions" % "6.1.0",
+ "com.h2database" % "h2" % "2.2.224"
+)
// Adds additional packages into Twirl
//TwirlKeys.templateImports += "com.example.controllers._"
// Adds additional packages into conf/routes
// play.sbt.routes.RoutesKeys.routesImport += "com.example.binders._"
# https://www.playframework.com/documentation/latest/Configuration
+slick.dbs.default.profile="slick.jdbc.H2Profile$"
+slick.dbs.default.db.driver="org.h2.Driver"
+slick.dbs.default.db.url="jdbc:h2:mem:play;DB_CLOSE_DELAY=-1"
+slick.dbs.default.db.user=sa
+slick.dbs.default.db.password=""
+
+play.evolutions.db.default.autoApply=true
それぞれのバージョンは下記を参考にして指定しました。
次にUSERSテーブル関連のマイグレーションファイルを作成します。
conf/evolutions/{DB名}/{数字}.sql
のファイルにクエリを書くことで、数字の小さいものからマイグレーションが実行されるようになります。
ファイル名の数字は自然数(e.g. 1, 2, ...)しかサポートされていないようです。
# --- !Ups
CREATE TABLE "USERS" (
"ID" INT AUTO_INCREMENT PRIMARY KEY,
"NAME" VARCHAR NOT NULL,
"AGE" INT NOT NULL
);
INSERT INTO "USERS" ("NAME", "AGE") VALUES ('Alice', 30);
INSERT INTO "USERS" ("NAME", "AGE") VALUES ('Bob', 25);
INSERT INTO "USERS" ("NAME", "AGE") VALUES ('Charlie', 35);
# --- !Downs
DROP TABLE "USERS";
次にUserModelを作成します。
package models
final case class User(
id: Option[Int],
name: String,
age: Int
)
次にUsersのDAOを作成します。
UsersTable
でテーブル情報を定義して、それを使ってall()
でUsersテーブルからデータを取得します。取得したデータはUserModelとして返却します。
package dao
import scala.concurrent.Future
import javax.inject.Inject
import play.api.db.slick.DatabaseConfigProvider
import play.api.db.slick.HasDatabaseConfigProvider
import scala.concurrent.ExecutionContext
import slick.jdbc.JdbcProfile
import models.User
class UsersDao @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
extends HasDatabaseConfigProvider[JdbcProfile] {
import profile.api._
private val Users = TableQuery[UsersTable]
def all()(implicit ec: ExecutionContext): Future[Seq[User]] = db.run(Users.result)
private class UsersTable(tag: Tag) extends Table[User](tag, "USERS") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME")
def age = column[Int]("AGE")
def * = (id.?, name, age) <> ((User.apply _).tupled, User.unapply)
}
}
ControllerからDAOを呼ぶようにします。
package controllers
+import scala.concurrent.ExecutionContext
import javax.inject.Inject
import javax.inject.Singleton
import play.api.mvc.AbstractController
import play.api.mvc.Action
import play.api.mvc.AnyContent
import play.api.mvc.ControllerComponents
import play.api.mvc.Request
import scala.concurrent.Future
+import dao.UsersDao
+import models.User
@Singleton
-class UsersController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
+class UsersController @Inject()(dao: UsersDao, cc: ControllerComponents)(implicit ec: ExecutionContext) extends AbstractController(cc) {
- def index = Action { implicit request: Request[AnyContent] =>
- Ok("OK!!")
+ def index = Action.async { implicit request =>
+ dao.all().map {
+ u => Ok(views.html.users(u))
+ }
}
}
最後にユーザー一覧画面のhtmlを作成します。
Seq[User]
を受け取って表として表示するだけのものです。
@(users: Seq[User])
@main("Users") {
<div>
<div id="users">
<h1>Users</h1>
<table>
<tr>
<th>Id</th>
<th>Name</th>
<th>Age</th>
</tr>
@for(u <- users){
<tr>
<td>@u.id</td>
<td>@u.name</td>
<td>@u.age</td>
</tr>
}
</table>
</div>
</div>
}
コードの変更が終わったらreload, runします。
sbt reload
sbt run
conf/evolutions/default/1.sql
で投入した初期ユーザーらが表示されるようになりました。
Step3: ユーザーを登録できる
ユーザーの登録ができるようにDAOにinsert()
を追加します。
package dao
import scala.concurrent.Future
import javax.inject.Inject
import play.api.db.slick.DatabaseConfigProvider
import play.api.db.slick.HasDatabaseConfigProvider
import scala.concurrent.ExecutionContext
import slick.jdbc.JdbcProfile
import models.User
class UsersDao @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
extends HasDatabaseConfigProvider[JdbcProfile] {
import profile.api._
private val Users = TableQuery[UsersTable]
def all()(implicit ec: ExecutionContext): Future[Seq[User]] = db.run(Users.result)
+ def insert(u: User)(implicit ec: ExecutionContext): Future[Unit] = db.run(Users += u).map { _ => () }
+
private class UsersTable(tag: Tag) extends Table[User](tag, "USERS") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME")
def age = column[Int]("AGE")
def * = (id.?, name, age) <> ((User.apply _).tupled, User.unapply)
}
}
次にControllerを変更します。
ユーザー登録はPOSTメソッドのpayloadを元に行うようにするので、payloadのマッピング(userForm
)を定義します。
また、create()
を追加し、insert()
を呼ぶようにします。
package controllers
import scala.concurrent.ExecutionContext
import javax.inject.Inject
import javax.inject.Singleton
import play.api.mvc.AbstractController
import play.api.mvc.Action
import play.api.mvc.AnyContent
import play.api.mvc.ControllerComponents
import play.api.mvc.Request
+import play.api.data.Form
+import play.api.data.Forms.mapping
+import play.api.data.Forms.text
+import play.api.data.Forms.number
import dao.UsersDao
import models.User
@Singleton
class UsersController @Inject()(dao: UsersDao, cc: ControllerComponents)(implicit ec: ExecutionContext) extends AbstractController(cc) {
def index = Action.async { implicit request =>
dao.all().map {
u => Ok(views.html.users(u))
}
}
+
+ def create = Action.async { implicit request =>
+ val user: User = userForm.bindFromRequest.get
+ dao.insert(user).map(_ => Redirect(routes.UsersController.index))
+ }
+
+ val userForm = Form(
+ mapping(
+ "name" -> text,
+ "age" -> number
+ )((name, age) => User(None, name, age))
+ (u => Some((u.name, u.age)))
+ )
}
Controllerに追加したcreate()
とPOST /users
をマッピングします。
# Routes
# This file defines all application routes (Higher priority routes first)
# https://www.playframework.com/documentation/latest/ScalaRouting
# ~~~~
# An example controller showing a sample home page
GET / controllers.HomeController.index()
GET /users controllers.UsersController.index()
+POST /users controllers.UsersController.create()
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
最後にhtmlにユーザー登録用のフォームを作成します。
※<div>
がハイライトされていますが、Zennのdiff表現の仕様(行の先頭の<
が差分の表現だと認識されている)によるもので、実際には差分はありません。
-@(users: Seq[User])
+@(users: Seq[User])(implicit request: RequestHeader)
+@import views.html.helper.CSRF
@main("Users") {
<div>
<div id="users">
+ <h1>Create User</h1>
+ <form action="/users" method="POST">
+ @CSRF.formField
+ <input name="name" type="text" placeholder="name" />
+ <input name="age" type="number" placeholder="age" />
+ <input type="submit" />
+ </form>
<h1>Users</h1>
<table>
<tr>
<th>Id</th>
<th>Name</th>
<th>Age</th>
</tr>
@for(u <- users){
<tr>
<td>@u.id</td>
<td>@u.name</td>
<td>@u.age</td>
</tr>
}
</table>
</div>
</div>
}
以上の変更を行ったあとの画面はこうなりました。
フォームにnameとageを入力してsubmitボタンを押して、ユーザー登録ができるようになっています。
ユーザーと企業の関連付け
続いて、ユーザーを登録する際に所属企業も設定できるようにしてみます。
まず企業データを保持するテーブルを作成します。
# --- !Ups
CREATE TABLE "COMPANIES" (
"ID" INT AUTO_INCREMENT PRIMARY KEY,
"NAME" VARCHAR NOT NULL,
"ADDRESS" VARCHAR
);
INSERT INTO "COMPANIES" ("NAME", "ADDRESS") VALUES ('A', 'Tokyo');
INSERT INTO "COMPANIES" ("NAME", "ADDRESS") VALUES ('B', 'Osaka');
INSERT INTO "COMPANIES" ("NAME", "ADDRESS") VALUES ('C', 'Fukuoka');
# --- !Downs
DROP TABLE "COMPANIES";
また、USERS
テーブル、UserModel
にCompanyIdを追加します。
# --- !Ups
ALTER TABLE "USERS"
ADD COLUMN "COMPANY_ID" INT;
UPDATE "USERS" SET "COMPANY_ID" = 1 WHERE "ID" = 1;
UPDATE "USERS" SET "COMPANY_ID" = 1 WHERE "ID" = 2;
UPDATE "USERS" SET "COMPANY_ID" = 2 WHERE "ID" = 3;
ALTER TABLE "USERS"
ADD CONSTRAINT FK_COMPANY_ID
FOREIGN KEY ("COMPANY_ID") REFERENCES "COMPANIES"("ID");
# --- !Downs
ALTER TABLE "USERS"
DROP CONSTRAINT "FK_COMPANY_ID";
ALTER TABLE "USERS"
DROP COLUMN "COMPANY_ID";
package models
final case class User(
id: Option[Int],
name: String,
- age: Int
+ age: Int,
+ companyId: Int
)
次にCompanyModelを作成します。
package models
final case class Company(
id: Option[Int],
name: String,
address: String
)
続いてUsersDaoにCompanyを紐づけてUserを取得するメソッドを作成します。
後でリファクタをしますが、一旦愚直に書いてみます。
package dao
import scala.concurrent.Future
import javax.inject.Inject
import play.api.db.slick.DatabaseConfigProvider
import play.api.db.slick.HasDatabaseConfigProvider
import scala.concurrent.ExecutionContext
import slick.jdbc.JdbcProfile
import models.User
+import models.Company
class UsersDao @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
extends HasDatabaseConfigProvider[JdbcProfile] {
import profile.api._
private val Users = TableQuery[UsersTable]
+ private val Companies = TableQuery[CompaniesTable]
def all()(implicit ec: ExecutionContext): Future[Seq[User]] = db.run(Users.result)
def insert(u: User)(implicit ec: ExecutionContext): Future[Unit] = db.run(Users += u).map { _ => () }
+
+ def allWithCompany()(implicit ec: ExecutionContext): Future[Seq[(User, Option[Company])]] = {
+ val query = for {
+ (user, company) <- Users joinLeft Companies on (_.companyId === _.id)
+ } yield (user, company)
+ db.run(query.result)
+ }
private class UsersTable(tag: Tag) extends Table[User](tag, "USERS") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME")
def age = column[Int]("AGE")
+ def companyId = column[Option[Int]]("COMPANY_ID")
- def * = (id.?, name, age) <> ((User.apply _).tupled, User.unapply)
+ def * = (id.?, name, age, companyId) <> ((User.apply _).tupled, User.unapply)
}
+
+ private class CompaniesTable(tag: Tag) extends Table[Company](tag, "COMPANIES") {
+ def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
+ def name = column[String]("NAME")
+ def address = column[String]("ADDRESS")
+
+ def * = (id.?, name, address) <> ((Company.apply _).tupled, Company.unapply)
+ }
}
UsersController
で呼ぶメソッドをall()
からallWithCompany()
に変更します。
package controllers
import scala.concurrent.ExecutionContext
import javax.inject.Inject
import javax.inject.Singleton
import play.api.mvc.AbstractController
import play.api.mvc.Action
import play.api.mvc.AnyContent
import play.api.mvc.ControllerComponents
import play.api.mvc.Request
import play.api.data.Form
import play.api.data.Forms.mapping
import play.api.data.Forms.text
import play.api.data.Forms.number
+import play.api.data.Forms.optional
import dao.UsersDao
import models.User
@Singleton
class UsersController @Inject()(dao: UsersDao, cc: ControllerComponents)(implicit ec: ExecutionContext) extends AbstractController(cc) {
def index = Action.async { implicit request =>
- dao.all().map {
- u => Ok(views.html.users(u))
+ dao.allWithCompany().map {
+ result => Ok(views.html.users(result))
}
}
def create = Action.async { implicit request =>
val user: User = userForm.bindFromRequest.get
dao.insert(user).map(_ => Redirect(routes.UsersController.index))
}
val userForm = Form(
mapping(
"name" -> text,
- "age" -> number
- )((name, age) => User(None, name, age))
- (u => Some((u.name, u.age)))
+ "age" -> number,
+ "companyId" -> optional(number)
+ )((name, age, companyId) => User(None, name, age, companyId))
+ (u => Some((u.name, u.age, u.companyId)))
)
}
最後にusers.scala.html
の入力フォームやユーザー一覧の列にCompanyを追加します。
※<div>
がハイライトされていますが、Zennのdiff表現の仕様(行の先頭の<
が差分の表現だと認識されている)によるもので、実際には差分はありません。
-@(users: Seq[User])(implicit request: RequestHeader)
+@(data: Seq[(User, Option[Company])])(implicit request: RequestHeader)
@import views.html.helper.CSRF
@main("Users") {
<div>
<div id="users">
<h1>Create User here:</h1>
<form action="/users" method="POST">
@CSRF.formField
<input name="name" type="text" placeholder="name" />
<input name="age" type="number" placeholder="age" />
+ <input name="companyId" type="number" placeholder="companyId" />
<input type="submit" />
</form>
<h1>Users</h1>
<table>
<tr>
<th>Id</th>
<th>Name</th>
<th>Age</th>
<th>Company</th>
</tr>
- @for(u <- users){
+ @for((user, company) <- data){
<tr>
- <td>@u.id</td>
- <td>@u.name</td>
- <td>@u.age</td>
+ <td>@user.id</td>
+ <td>@user.name</td>
+ <td>@user.age</td>
+ <td>@company.map(_.name)</td>
</tr>
}
</table>
</div>
</div>
}
ユーザー登録時に企業IDを指定できるようになり、ユーザー一覧にも企業名が表示されるようになりました。
リファクタリング
今の状態ではUsersDao
でcompany関連の定義も持ってしまっています。
例えばCompanyDao
を作成しようとすると、こちらでもCOMPANYテーブル定義情報を持つ必要が出てきます。
テーブル定義情報をまとめて保持するtraitクラスを作成し、各DAOはそれを継承することで責務を分離させてみます。
まず、テーブル定義情報をまとめて保持するTables.scalaを作成します。
package dao
import slick.jdbc.JdbcProfile
import play.api.db.slick.HasDatabaseConfigProvider
import models._
trait Tables extends HasDatabaseConfigProvider[JdbcProfile] {
import profile.api._
class UsersTable(tag: Tag) extends Table[User](tag, "USERS") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME")
def age = column[Int]("AGE")
def companyId = column[Option[Int]]("COMPANY_ID")
def * = (id.?, name, age, companyId) <> ((User.apply _).tupled, User.unapply)
}
class CompaniesTable(tag: Tag) extends Table[Company](tag, "COMPANIES") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME")
def address = column[String]("ADDRESS")
def * = (id.?, name, address) <> ((Company.apply _).tupled, Company.unapply)
}
val Users = TableQuery[UsersTable]
val Companies = TableQuery[CompaniesTable]
}
UsersDao
でTables
を継承し、不要になった定義を削除します。
package dao
import scala.concurrent.Future
import javax.inject.Inject
import play.api.db.slick.DatabaseConfigProvider
import play.api.db.slick.HasDatabaseConfigProvider
import scala.concurrent.ExecutionContext
import slick.jdbc.JdbcProfile
import models.User
import models.Company
class UsersDao @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
-extends HasDatabaseConfigProvider[JdbcProfile] {
+extends HasDatabaseConfigProvider[JdbcProfile] with Tables {
import profile.api._
-
- private val Users = TableQuery[UsersTable]
- private val Companies = TableQuery[CompaniesTable]
def all()(implicit ec: ExecutionContext): Future[Seq[User]] = db.run(Users.result)
def insert(u: User)(implicit ec: ExecutionContext): Future[Unit] = db.run(Users += u).map { _ => () }
def allWithCompany()(implicit ec: ExecutionContext): Future[Seq[(User, Option[Company])]] = {
val query = for {
(user, company) <- Users joinLeft Companies on (_.companyId === _.id)
} yield (user, company)
db.run(query.result)
}
-
- private class UsersTable(tag: Tag) extends Table[User](tag, "USERS") {
- def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
- def name = column[String]("NAME")
- def age = column[Int]("AGE")
- def companyId = column[Option[Int]]("COMPANY_ID")
-
- def * = (id.?, name, age, companyId) <> ((User.apply _).tupled, User.unapply)
- }
-
- private class CompaniesTable(tag: Tag) extends Table[Company](tag, "COMPANIES") {
- def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
- def name = column[String]("NAME")
- def address = column[String]("ADDRESS")
-
- def * = (id.?, name, address) <> ((Company.apply _).tupled, Company.unapply)
- }
}
この状態で画面操作をしてみて、これまで通りに操作ができていればリファクタ完了です。
バリデーション
最後にユーザー登録時のバリデーションを実装してみます。
※Form等を使ってそれぞれの項目にバリデーション(e.g. 最大文字長)を定義することもできますが、勉強用に自分で実装してみます。
User.name
について下記のバリデーションを実施するメソッドを作成します。
- 英字大文字始まりであること
- 文字長が256以下であること
package controllers
import models.User
object UserValidation {
def validate(input: User): Either[Seq[String], User] = {
val errors: Seq[String] = Seq(
if (!nameStartWithUppercase(input.name)) Some("Name must start with uppercase.") else None,
if (!nameLessThan256Chars(input.name)) Some("Name must be 256 characters or fewer.") else None
).flatten
if (errors.isEmpty) Right(input)
else Left(errors)
}
def nameStartWithUppercase(name: String): Boolean = {
name.headOption.exists(_.isUpper)
}
def nameLessThan256Chars(name: String): Boolean = {
name.length <= 256
}
}
Contollerでバリデーションメソッドを呼ぶようにします。
Right
が返ってきた場合は、これまで通りUsersController.index
にリダイレクトを行い、Left
が返ってきた場合は、エラー文言を表示するようにします。
package controllers
import scala.concurrent.ExecutionContext
import javax.inject.Inject
import javax.inject.Singleton
import play.api.mvc.AbstractController
import play.api.mvc.Action
import play.api.mvc.AnyContent
import play.api.mvc.ControllerComponents
import play.api.mvc.Request
import play.api.data.Form
import play.api.data.Forms.mapping
import play.api.data.Forms.text
import play.api.data.Forms.number
import play.api.data.Forms.optional
import dao.UsersDao
import models.User
@Singleton
class UsersController @Inject()(dao: UsersDao, cc: ControllerComponents)(implicit ec: ExecutionContext) extends AbstractController(cc) {
def index = Action.async { implicit request =>
dao.allWithCompany().map {
result => Ok(views.html.users(result))
}
}
def create = Action.async { implicit request =>
- val user: User = userForm.bindFromRequest.get
- dao.insert(user).map(_ => Redirect(routes.UsersController.index))
+ userForm.bindFromRequest.fold(
+ formWithErrors => {
+ Future.successful(BadRequest("Form input has errors"))
+ },
+ userData => {
+ UserValidation.validate(userData) match {
+ case Right(user) =>
+ dao.insert(user).map(_ => Redirect(routes.UsersController.index))
+ case Left(errors) =>
+ Future.successful(BadRequest(errors.mkString("\n")))
+ }
+ }
+ )
}
val userForm = Form(
mapping(
"name" -> text,
"age" -> number,
"companyId" -> optional(number)
)((name, age, companyId) => User(None, name, age, companyId))
(u => Some((u.name, u.age, u.companyId)))
)
}
画面から、下記データを入力してみます。
- name:
12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
(257文字) - age:
20
- companyId:
1
正常系も確認して問題なく動作すれば終了です。
まとめ
Scalaの勉強としてPlayを使ってサンプルアプリケーションを作成してみました。特にFuture
、Either
については調べたことを記載しきれていないので、時間があれば別途まとめてみようと思います。
Discussion