1. tsuwatch

    Fix typo.

    tsuwatch
Changes in tags
Changes in body
Source | HTML | Preview
@@ -1,1015 +1,1015 @@
Play Framework 2.3.x と Scala を使った Web システム開発について勉強した際の内容を自分用のメモとしてまとめました。
次に示すソフトウェアがインストール済みの環境で、「顧客情報の登録/検索/更新/削除」、「商品情報の登録/検索/更新/削除」、そして、「受注登録」を行うための簡単な 受注管理システムを作成することを目指します。
|ソフトウェア名 |バージョン |入手元 |
|-----------|---------|------|
|OS |Windows 7|-|
|JDK |8u11(jdk1.8.0_05) |http://www.oracle.com/|
|Play |2.3.2 |http://www.playframework.com/|
|Eclipse |4.3.2(Pleiades版) |http://mergedoc.sourceforge.jp/|
|Scala IDE for Eclipse|-|http://download.scala-ide.org/sdk/helium/e38/scala211/stable/site|
## 1: Play Framework 2.3.x で 「Hello World」
まず、手始めに Play Framework 2.3.x (Scala) を使って 「Hello World」 を表示するだけの簡単な Web アプリケーションを作成します。
### 手順 1-1: スタートメニューから "Windows PowerShell" を起動します。
### 手順 1-2: PowerShell のコンソール画面で下記コマンドを入力し、`play-scala` という名前のテンプレートから新規プロジェクトを作成します
```ps1
PS> Set-Location "$HOME" ←任意のフォルダーで構いません。
PS> & activator new "order-management-system" "play-scala"
```
### 手順 1-3: PowerShell のコンソール画面で下記コマンドを入力し、 Eclipse のプロジェクトとして読み込み可能な形式に変換します
```ps1
PS> Set-Location ".\order-management-system"
PS> & activator eclipse
```
Play Framework は「Eclipse」の他にも、IDE として「Intellij IDEA」もサポートしています。「Intellij IDEA」で利用する場合は、`& activator idea` と入力します。
### 手順 1-4: スタートメニューから "Eclipse" を起動します。
### 手順 1-5: Eclipse のメニューから [ファイル(F)] → [インポート(I)...] を選択しクリックします
### 手順 1-6: [一般] → [既存プロジェクトをワークスペースへ] を選択し、[次へ(N)>] ボタンをクリックします
### 手順 1-7: "ルート・ディレクトリーの選択(T):" で作成したプロジェクトのフォルダーを選択し、[完了(F)] ボタンをクリックします
### 手順 1-8: ファイル `conf/routes` を確認します
```scala:conf/routes
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# Home page
GET / controllers.Application.index
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file)
```
ホームページ(/)に GET リクエストがあった際に、`controllers` パッケージにある `Application` オブジェクトの `index` メソッドが実行されるという意味の構文が記述されていることを確かめます。
### 手順 1-9: ファイル `app/controllers/Application.scala` を編集します
```scala:app/controllers/Application.scala
package controllers
import play.api._
import play.api.mvc._
object Application extends Controller {
def index = Action {
Ok("Hello world")
}
}
```
Play Framework では、HTTP リクエストを Action オブジェクトを使って処理します。
ここでは、HTTP リクエストがあった場合に、`ステータス 200 OK` のレスポンスと共に、"Hello world" 文字列を返す記述をしています。
### 手順 1-10: PowerShell のコンソール画面で下記コマンドを入力し、 作成したアプリケーションを起動します
```ps1
PS> & activator run
```
### 手順 1-11: ブラウザで、`http://localhost:9000/` にアクセスし、「Hello World」が表示されることを確かめます。
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="http://cdn-ak.f.st-hatena.com/images/fotolife/k/kukita/20140805/20140805230304.png" class="hatena-fotolife" itemprop="image"></span></p>
### 手順 1-12: PowerShell のコンソール画面で Ctrl+D キーを入力し、 アプリケーションを停止します
なお、 Play Framework 2 は、アプリケーションが起動したままの状態でソースコードを編集した場合でも、ブラウザからのアクセス時にコンパイルが自動的に行われます。
## 2: Play Framework 2.3.x と Play-Slick を使った CRUD アプリケーションの作成
続いて、Scala のためのデータベースラッパー「Slick」と Slick を Play Framework 2 で利用するためのプラグイン「Play-Slick」を使って、顧客情報の「登録(Create)」、「参照/検索(Reference)」、「更新(Update)」、「削除(Delete)」を行うための簡単な CRUD アプリケーションを作成します。
Play Framework 2.3.x では、Model 層の実装に Anorm を標準採用していますが、ここでは、次期バージョンから採用予定とされている Slick というライブラリを使用します。
### 手順 2-1: "build.sbt" に Slick に関連するライブラリのリポジトリを追記します
```scala:build.sbt
name := """order-management-system"""
version := "1.0-SNAPSHOT"
lazy val root = (project in file(".")).enablePlugins(PlayScala)
scalaVersion := "2.11.1"
libraryDependencies ++= Seq(
jdbc,
anorm,
cache,
ws,
"com.typesafe.slick" %% "slick" % "2.1.0", # 追記
"org.slf4j" % "slf4j-nop" % "1.6.4", # 追記
"com.typesafe.play" %% "play-slick" % "0.8.0" # 追記
)
```
### 手順 2-2: PowerShell のコンソール画面で下記コマンドを入力し、依存ライブラリをダウンロードします
```ps1
PS> & activator update
PS> & activator eclipse
```
### 手順 2-3: ファイル "conf/application.conf" を編集します
```ps1:conf/application.conf
中略
# Database configuration
# ~~~~~
# You can declare as many datasources as you want.
# By convention, the default datasource is named `default`
#
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:oms;MODE=MYSQL;DB_CLOSE_DELAY=-1"
db.default.user=sa
db.default.password=""
slick.default="models.*"
中略
```
H2 のインメモリ DB を使って、"oms" という名前のデータベースを作成/使用するための設定を行っています。
また、MySQL の動作を真似るために `MODE=MYSQL` パラメーター、インメモリ DB のリセットを防ぐために `DB_CLOSE_DELAY=-1` パラメーターを"db.default.url"の値に与えています。
### 手順 2-4: ファイル `app/models/customer.scala` を作成しモデル層の実装をします
```scala:app/models/customer.scala
package models
/**
* ① Slick 関連のパッケージのインポート
*/
import play.api.db.slick.Config.driver.simple._
/**
* ② DTO の定義
*/
case class Customer(ID: Long, name: String, email: String, tel: String, address: String, comment: String)
/**
* ③ テーブルスキーマの定義
*/
class CustomerTable(tag: Tag) extends Table[Customer](tag, "customers") {
def ID = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name", O.NotNull)
def email = column[String]("email", O.NotNull)
def tel = column[String]("tel", O.NotNull )
def address = column[String]("address", O.NotNull)
def comment = column[String]("comment", O.NotNull)
def * = (ID, name, email, tel, address, comment) <> (Customer.tupled, Customer.unapply)
}
/**
* ④ DAO の定義
*/
object CustomerDAO {
lazy val customerQuery = TableQuery[CustomerTable]
/**
* キーワード検索
* @param word
*/
def search(word: String)(implicit s: Session): List[Customer] = {
customerQuery.filter(row => (row.name like "%"+word+"%") || (row.email like "%"+word+"%") || (row.tel like "%"+word+"%") || (row.address like "%"+word+"%") || (row.comment like "%"+word+"%")).list
}
/**
* ID検索
* @param ID
*/
def searchByID(ID: Long)(implicit s: Session): Customer = {
customerQuery.filter(_.ID === ID).first
}
/**
* 作成
* @param customer
*/
def create(customer: Customer)(implicit s: Session) {
customerQuery.insert(customer)
}
/**
* 更新
* @param customer
*/
def update(customer: Customer)(implicit s: Session) {
customerQuery.filter(_.ID === customer.ID).update(customer)
}
/**
* 削除
* @param customer
*/
def remove(customer: Customer)(implicit s: Session) {
customerQuery.filter(_.ID === customer.ID).delete
}
}
```
#### ① Slick 関連のパッケージのインポート
```scala
/**
* ① Slick 関連のパッケージのインポート
*/
import play.api.db.slick.Config.driver.simple._
```
"play-slick"のパッケージをインポートしています。
先ほど記述した設定ファイルから自動的にドライバ関連のパッケージも読み込まれますので利用するDBMSの種類を指定する必要はありません。
#### ② DTO の定義
```scala
/**
* ② DTO の定義
*/
case class Customer(ID: Long, name: String, email: String, tel: String, address: String, comment: String)
```
Data Transfer Object(DTO)のクラスを定義します。
Scala では、`case class` として定義することでコンストラクタや getter 等が自動的に定義されます。
[参考]
[Data Transfer Object - ウィキペディア](http://ja.wikipedia.org/wiki/Data_Transfer_Object)
#### ③ テーブルスキーマの定義
```scala
/**
* ③ テーブルスキーマの定義
*/
class CustomerTable(tag: Tag) extends Table[Customer](tag, "customers") {
def ID = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name", O.NotNull)
def email = column[String]("email", O.NotNull)
def tel = column[String]("tel", O.NotNull )
def address = column[String]("address", O.NotNull)
def comment = column[String]("comment", O.NotNull)
def * = (ID, name, email, tel, address, comment) <> (Customer.tupled, Customer.unapply)
}
```
この形式で記述することで、`CREATE TABLE` 文と `DROP TABLE` 文を自動的に生成します。
#### ④ DAO の定義
```scala
/**
* ④ DAO の定義
*/
object CustomerDAO {
lazy val customerQuery = TableQuery[CustomerTable]
/**
* キーワード検索
* @param word
*/
def search(word: String)(implicit s: Session): List[Customer] = {
customerQuery.filter(row => (row.name like "%"+word+"%") || (row.email like "%"+word+"%") || (row.tel like "%"+word+"%") || (row.address like "%"+word+"%") || (row.comment like "%"+word+"%")).list
}
/**
* ID検索
* @param ID
*/
def searchByID(ID: Long)(implicit s: Session): Customer = {
customerQuery.filter(_.ID === ID).first
}
/**
* 作成
* @param customer
*/
def create(customer: Customer)(implicit s: Session) {
customerQuery.insert(customer)
}
/**
* 更新
* @param customer
*/
def update(customer: Customer)(implicit s: Session) {
customerQuery.filter(_.ID === customer.ID).update(customer)
}
/**
* 削除
* @param customer
*/
def remove(customer: Customer)(implicit s: Session) {
customerQuery.filter(_.ID === customer.ID).delete
}
}
```
Data Access Object(DAO)を定義しています。
Scalaでは、`object` と定義することで簡単にシングルトン化されたオブジェクトを作成することができます。
また、メソッドしては、CRUD アプリケーションに必要な基本的なクエリーを 5 つ定義しています。
[参考]
[Data Access Object - ウィキペディア](http://ja.wikipedia.org/wiki/Data_Access_Object)
### 手順 2-5: PowerShell のコンソール画面で下記コマンドを入力し、 作成したアプリケーションを起動します
```ps1
PS> & activator run
```
### 手順 2-6: ブラウザで、`http://localhost:9000/` にアクセスし、「Database 'default' needs evolution!」と書かれた画面が表示されることを確かめます。
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="http://cdn-ak.f.st-hatena.com/images/fotolife/k/kukita/20140807/20140807110913.png" class="hatena-fotolife" itemprop="image"></span></p>
ここで、`Apply this script now!` ボタンをクリックすると自動生成された SQL 文が実行されます。
### 手順 2-7: ファイル `conf/routes` を編集します
```scala:conf/routes
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# Home page
GET / controllers.Application.index
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file)
#
# Customers Management
#
# Create customer infomation.
GET /customers/create controllers.CustomerController.showCreateForm()
POST /customers/create controllers.CustomerController.create()
# Search customer infomation.
GET /customers/search controllers.CustomerController.search(word: String ?= "")
# Update customer infomation.
GET /customers/:id/update controllers.CustomerController.showUpdateForm(id: Long)
POST /customers/:id/update controllers.CustomerController.update(id: Long)
# Remove customer infomation
GET /customers/:id/remove controllers.CustomerController.remove(id: Long)
```
顧客情報を「作成」「検索」「更新」「削除」する画面をそれぞれ定義します。
なお、余談ですが、このファイルはコメント等に日本語を使うとエラーになってしまうので注意が必要です。
### 手順 2-8: ファイル `app/controllers/customerController.scala` を作成します
```scala:app/controllers/customerController.scala
package controllers
/**
* ① パッケージのインポート
*/
import play.api._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import play.api.db.slick._
import models._
/**
* ② コントローラーオブジェクトの定義
*/
object CustomerController extends Controller {
/**
* ③ フォームの定義
*/
val customerForm = Form(
mapping(
"ID" -> longNumber,
"name" -> nonEmptyText(maxLength = 140),
"email" -> nonEmptyText(maxLength = 140),
"tel" -> nonEmptyText(maxLength = 140),
"address" -> nonEmptyText(maxLength = 140),
"comment" -> text(maxLength = 140)
)(Customer.apply)(Customer.unapply)
)
/**
* ④ 顧客情報登録フォーム表示アクションメソッドの定義
*/
def showCreateForm() = Action { request =>
Ok(views.html.customerCreateForm(customerForm))
}
/**
* ⑤ 顧客情報登録アクションメソッドの定義
*/
def create() = DBAction { implicit rs =>
customerForm.bindFromRequest.fold(
errors => BadRequest(views.html.customerCreateForm(errors)),
customer => {
CustomerDAO.create(customer)
Redirect(routes.CustomerController.search())
}
)
}
/**
* ⑥ 顧客情報検索アクションメソッドの定義
*/
def search(word: String) = TODO
/**
* ⑦ 顧客情報更新フォーム表示アクションメソッドの定義
*/
def showUpdateForm(ID: Long) = TODO
/**
* ⑧ 顧客情報更新アクションメソッドの定義
*/
def update(ID: Long) = TODO
/**
* ⑨ 顧客情報削除アクションメソッドの定義
*/
def remove(ID: Long) =TODO
}
```
#### ① パッケージのインポート
```scala
/**
* ① パッケージのインポート
*/
import play.api._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import play.api.db.slick._
import models._
```
最初の2行で、コントローラー層として利用するために必要なパッケージをインポートしています。
次の2行は、Play Framework 2 に用意されているHTTP フォームデータの送信とバリデーションを行うヘルパーを利用するために必要なパッケージをインポートしています。
最後の2行は、先ほど定義した DTO と DAO を利用するために、play-slick のパッケージとモデル層のパッケージをインポートしています。
#### ② コントローラーオブジェクトの定義
```scala
/**
* ② コントローラーオブジェクトの定義
*/
object CustomerController extends Controller {
中略
```
続いてコントローラーオブジェクトの定義を行います。`Controller` クラスを継承する形で、先ほど `conf/routes` 内で定義した全てのコントローラーとメソッドを定義する必要があります。
#### ③ HTTP フォームデータの定義
```scala
/**
* ③ HTTP フォームデータの定義
*/
val customerForm = Form(
mapping(
"ID" -> longNumber,
"name" -> nonEmptyText(maxLength = 140),
"email" -> nonEmptyText(maxLength = 140),
"tel" -> nonEmptyText(maxLength = 140),
"address" -> nonEmptyText(maxLength = 140),
"comment" -> text(maxLength = 140)
)(Customer.apply)(Customer.unapply)
)
```
Play Framework 2 では、HTTP フォームデータの送信とバリデーションを行うヘルパーが用意されています。
ここでは、先ほど DTO として定義した `Customer` にラップするフォームを定義しています。
詳細については、下記の公式ドキュメントを参照してください。
[[http://www.playframework-ja.org/documentation/2.0.8/ScalaForms]](http://www.playframework-ja.org/documentation/2.0.8/ScalaForms)
#### ④ 顧客情報登録フォーム表示アクションメソッドの定義
```scala
/**
* ④ 顧客情報登録フォーム表示アクションメソッドの定義
*/
def showCreateForm() = Action { request =>
Ok(views.html.customerCreateForm(customerForm))
}
```
`conf/routes` 内で定義した `showCreateForm()` メソッドの定義を行います。
Play Framework では、HTTP リクエストを `Action` オブジェクトを使って処理します。
ここでは、HTTP リクエストがあった場合に、`ステータス 200 OK` のレスポンスと共に、後ほど定義する View の結果を返すように定義します。
詳細については、下記の公式ドキュメントを参照してください。
[[http://www.playframework-ja.org/documentation/2.0.8/ScalaActions]](http://www.playframework-ja.org/documentation/2.0.8/ScalaActions)
#### ⑤ 顧客情報登録アクションメソッドの定義
```scala
/**
* ⑤ 顧客情報登録アクションメソッドの定義
*/
def create() = DBAction { implicit rs =>
customerForm.bindFromRequest.fold(
errors => BadRequest(views.html.customerCreateForm(errors)),
customer => {
CustomerDAO.create(customer)
Redirect(routes.CustomerController.search())
}
)
}
```
`conf/routes` 内で定義した `Create()` メソッドの定義を行います。
play-slick の `DBAction` オブジェクトを使うことで、DB とのセッションを自動的に生成することができます。
ここでは、`bindFromRequest.fold()` メソッドを使って、HTTP フォームデータの定義で行っているバリデーションのバインドエラー処理を行っています。
#### ⑥ 顧客情報検索アクションメソッドの定義以降
```scala
/**
* ⑥ 顧客情報検索アクションメソッドの定義
*/
def search(word: String) = TODO
中略
```
前述したように、`conf/routes` 内で定義した全てのメソッドについて定義する必要がありますが、ここでは TODO という空の `Action` オブジェクトの実装を利用します。
こうすることで、Play Framework 標準の "Not implemented yet" ページを表示させることができます。
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="http://cdn-ak.f.st-hatena.com/images/fotolife/k/kukita/20140805/20140805230411.png" class="hatena-fotolife" itemprop="image"></span></p>
### 手順 2-9: Play Framework 2.3.x で Twitter Bootstrap 3 を利用するためにファイル `app/views/main.html.scala` を編集します
Twitter Bootstrap 3 の公式ページを参考に記述します。
[[http://getbootstrap.com/getting-started/]](http://getbootstrap.com/getting-started/)
```html:app/views/main.html.scala`
@(title: String)(content: Html)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@title</title>
<!-- Bootstrap -->
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<section class="content">@content</section>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
</body>
</html>
```
### 手順 2-10: `app/views/header.scala.html` を作成します
```html:app/views/header.scala.html
<div class="navbar navbar-inverse navbar-static-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">受注管理システム</a>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav" role="menu">
<li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown">顧客情報管理</a>
<ul class="dropdown-menu">
<li><a role="menuitem" href="/customers/search">顧客情報一覧(検索/更新/削除)</a></li>
<li><a role="menuitem" href="/customers/create">顧客情報登録</a></li>
</ul>
</li>
</ul>
</div>
</div>
</div>
```
### 手順 2-11: `app/views/customerCreateForm.scala.html` を作成します
```html:app/views/customerCreateForm.scala.html`
@(customerForm: Form[Customer])
@import helper._
@main(title = "受注管理システム - 顧客情報登録") {
<!-- ヘッダー -->
@header()
<div class="row">
<div class="container">
<!-- サイドメニュー -->
<div class="hidden-xs col-sm-3 col-md-3 col-lg-3">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<ul class="nav nav-pills nav-stacked">
<li><a href="/customers/search">顧客情報一覧(検索/更新/削除)</a></li>
<li><a href="/customers/create">顧客情報登録</a></li>
</ul>
</div>
</div>
</div>
<!-- コンテンツ -->
<div class="col-xs-12 col-sm-9 col-md-9 col-lg-9">
<div class="row">
<!-- 見出し -->
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<h1>顧客情報登録</h1>
</div>
<!-- フォーム -->
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
@form(routes.CustomerController.create()) {
<fieldset>
<input type="hidden" name="ID" value="0">
@inputText(customerForm("name"), '_label -> "顧客氏名", 'size -> 30)
@inputText(customerForm("email"), '_label -> "メールアドレス",'type -> "email", 'size -> 30)
@inputText(customerForm("tel"), '_label -> "電話番号", 'size -> 30)
@inputText(customerForm("address"), '_label -> "住所", 'size -> 30)
@inputText(customerForm("comment"), '_label -> "備考", 'size -> 30)
</fieldset>
<div class="actions">
<input type="submit" class="btn btn-primary">
<a href="/customers/search" class="btn btn-danger">キャンセル</a>
</div>
}
</div>
</div>
</div>
</div>
}
```
Plaey Framework 2 では、`Scala ベースのテンプレートエンジン`が用意されています。
ここでは、`CustomerCreateForm.html.scala` という名前のファイルを作成したので、先ほどコントローラー内で定義した `views.html.customerCreateForm()` が生成されます。
詳細については、下記の公式ドキュメントを参考にしてください。
[[http://www.playframework-ja.org/documentation/2.0.8/ScalaTemplates]](http://www.playframework-ja.org/documentation/2.0.8/ScalaTemplates)
また、フォームの作成部分には、Play Framework 2 の `フォームテンプレートヘルパー` 機能を利用しています。
[[http://www.playframework-ja.org/documentation/2.0.8/ScalaFormHelpers]](http://www.playframework-ja.org/documentation/2.0.8/ScalaFormHelpers)
ポイントとしては、新規作成時点では、`ID(プライマリキー)` が未定なので、`input` タグの `hidden` タイプを使って値 `0` を渡しています。
### 手順 2-12: ここで一度ブラウザから `http://localhost:9000/customers/create` にアクセスして動作を確認します
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="http://cdn-ak.f.st-hatena.com/images/fotolife/k/kukita/20140805/20140805230441.png" class="hatena-fotolife" itemprop="image"></span></p>
フォームに何も入力しないで、 `送信` ボタンをクリックすると `This field is required` と表示されてバリデーションが働いていることが確認できます。
また、全ての必要項目に適当な値を入力して `送信` ボタンをクリックすると TODO 画面に遷移することが確認できます。(裏では DB にデータが挿入されています。)
ここまでの部分で、CRUD の 「C」の部分の実装ができたので、同様の流れで残りの部分を一気に実装してしまいます。
### 手順 2-13: ファイル `app/controllers/CustomerController.scala` を編集します
```scala:app/controllers/CustomerController.scala
package controllers
/**
* ① パッケージのインポート
*/
import play.api._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import play.api.db.slick._
import models._
/**
* ② コントローラーオブジェクトの定義
*/
object CustomerController extends Controller {
/**
* ③ HTTP フォームデータの定義
*/
val customerForm = Form(
mapping(
"ID" -> longNumber,
"name" -> nonEmptyText(maxLength = 140),
"email" -> nonEmptyText(maxLength = 140),
"tel" -> nonEmptyText(maxLength = 140),
"address" -> nonEmptyText(maxLength = 140),
"comment" -> text(maxLength = 140)
)(Customer.apply)(Customer.unapply)
)
/**
* ④ 顧客情報登録フォーム表示アクションメソッドの定義
*/
def showCreateForm() = Action { request =>
Ok(views.html.customerCreateForm(customerForm))
}
/**
* ⑤ 顧客情報登録アクションメソッドの定義
*/
def create() = DBAction { implicit rs =>
customerForm.bindFromRequest.fold(
errors => BadRequest(views.html.customerCreateForm(errors)),
customer => {
CustomerDAO.create(customer)
Redirect(routes.CustomerController.search())
}
)
}
/**
* ⑥ 顧客情報検索アクションメソッドの定義
*/
def search(word: String) = DBAction { implicit rs =>
Ok(views.html.customerSearch(word, CustomerDAO.search(word)))
}
/**
* ⑦ 顧客情報更新フォーム表示アクションメソッドの定義
*/
def showUpdateForm(ID: Long) = DBAction { implicit rs =>
Ok(views.html.customerUpdateForm(ID, customerForm.fill(CustomerDAO.searchByID(ID))))
}
/**
* ⑧ 顧客情報更新アクションメソッドの定義
*/
def update(ID: Long) = DBAction { implicit rs =>
customerForm.bindFromRequest.fold(
errors => BadRequest(views.html.customerUpdateForm(ID, errors)),
customer => {
CustomerDAO.update(customer)
Redirect(routes.CustomerController.search())
}
)
}
/**
* ⑨ 顧客情報削除アクションメソッドの定義
*/
def remove(ID: Long) = DBAction { implicit rs =>
CustomerDAO.remove(CustomerDAO.searchByID(ID))
Redirect(routes.CustomerController.search())
}
}
```
### 手順 2-14: ファイル `app/views/CustomerSearch.scala.html` を編集します
```html:app/views/CustomerSearch.scala.html
@(word: String, customers: List[Customer])
@main(title = "受注管理システム - 顧客情報一覧(検索/更新/削除)") {
<!-- ヘッダー -->
@header()
<div class="row">
<div class="container">
<!-- サイドメニュー -->
<div class="hidden-xs col-sm-3 col-md-3 col-lg-3">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<ul class="nav nav-pills nav-stacked">
<li><a href="/customers/search">顧客情報一覧(検索/更新/削除)</a></li>
<li><a href="/customers/create">顧客情報登録</a></li>
</ul>
</div>
</div>
</div>
<!-- コンテンツ -->
<div class="col-xs-12 col-sm-9 col-md-9 col-lg-9">
<div class="row">
<!-- 見出し -->
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<h1>顧客情報一覧(検索/更新/削除)</h1>
</div>
<!-- 検索フォーム -->
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
@helper.form(action=routes.CustomerController.search()) {
<input type="search" name="word" value="@word">
- <input type="submit" value="キーワード検索" class="byn btn-primary">
+ <input type="submit" value="キーワード検索" class="btn btn-primary">
}
</div>
<!-- テーブル -->
<div class="table-responsive col-xs-12 col-sm-12 col-md-12 col-lg-12">
<table class="table table-bordered table-striped ">
<thead>
<tr>
<th>顧客番号</th>
<th>顧客氏名</th>
<th>メールアドレス</th>
<th>電話番号</th>
<th>住所</th>
<th>備考</th>
<th align="center"></th>
</tr>
</thead>
<tbody>
@customers.map { customer =>
<tr>
<td align="right">@("%9d".format(customer.ID))</td>
<td>@customer.name</td>
<td>@customer.email</td>
<td>@customer.tel</td>
<td>@customer.address</td>
<td>@customer.comment</td>
<td align="center">
<a href="/customers/@customer.ID/update" class="btn btn-primary">更新</a>
<input type="button" class="btn btn-danger" value="削除" onClick='if(confirm("本当に削除してよろしいですか?")) {location.href="/customers/@customer.ID/remove"}'>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
}
```
### 手順 2-15: ファイル `app/views/CustomerUpdateForm.scala.html` を編集します
```html:app/views/CustomerUpdateForm.scala.html
@(ID: Long, customerForm: Form[Customer])
@import helper._
@main(title = "受注管理システム - 顧客情報更新") {
<!-- ヘッダー -->
@header()
<div class="row">
<div class="container">
<!-- サイドメニュー -->
<div class="hidden-xs col-sm-3 col-md-3 col-lg-3">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<ul class="nav nav-pills nav-stacked">
<li><a href="/customers/search">顧客情報一覧(検索/更新/削除)</a></li>
<li><a href="/customers/create">顧客情報登録</a></li>
</ul>
</div>
</div>
</div>
<!-- コンテンツ -->
<div class="col-xs-12 col-sm-9 col-md-9 col-lg-9">
<div class="row">
<!-- 見出し -->
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<h1>顧客情報更新</h1>
</div>
<!-- フォーム -->
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
@form(routes.CustomerController.update(ID)) {
<fieldset>
<input type="hidden" name="ID" value="@ID">
@inputText(customerForm("name"), '_label -> "顧客氏名", 'size -> 30)
@inputText(customerForm("email"), '_label -> "メールアドレス",'type -> "email", 'size -> 40)
@inputText(customerForm("tel"), '_label -> "電話番号", 'size -> 30)
@inputText(customerForm("address"), '_label -> "住所", 'size -> 30)
@inputText(customerForm("comment"), '_label -> "備考", 'size -> 30)
</fieldset>
<div class="actions">
<input type="submit" class="btn btn-primary">
<a href="/customers/search" class="btn btn-danger">キャンセル</a>
</div>
}
</div>
</div>
</div>
</div>
}
```
### 手順 2-16: ブラウザから `http://localhost:9000/customers/search` にアクセスします
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="http://cdn-ak.f.st-hatena.com/images/fotolife/k/kukita/20140805/20140805230507.png" class="hatena-fotolife" itemprop="image"></span></p>
先ほど登録したデータを参照することができます
### 手順 2-17: `更新` ボタンをクリックします
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="http://cdn-ak.f.st-hatena.com/images/fotolife/k/kukita/20140805/20140805230529.png" class="hatena-fotolife" itemprop="image"></span></p>
データの更新が可能です。データを更新すると `顧客情報一覧` 画面に戻ります。
### 手順 2-18: `顧客情報一覧` 画面に戻ります
### 手順 2-19: `削除` ボタンをクリックします
<p><span itemscope itemtype="http://schema.org/Photograph"><img src="http://cdn-ak.f.st-hatena.com/images/fotolife/k/kukita/20140805/20140805230550.png" class="hatena-fotolife" itemprop="image"></span></p>
確認画面が表示されるので `OK` ボタンをクリックするとデータが削除されます。
---
Play Framework 2.3.x を使って簡単な CRUD システムを構築することができました。
続いて、「商品管理」機能、「受注登録」機能の実装に移りたいところですが、長くなってしまったので、続きは以下に書きました。
【#Play】続・Play Framework 2.3 (Scala) を使った Web システム開発入門
[[http://qiita.com/kukita/items/178c6a274055d0139415]]
(http://qiita.com/kukita/items/178c6a274055d0139415)
以上