Play 2.5 と Slick 3.1 のチュートリアル

アカウンティング・サース・ジャパン Advent Calendar 2016 の7日目なのだ。

新しめの Play Framework で Slick を使ってみようとするとサクッと写経できるチュートリアルがない。ということで Slick Plugin for Play のサンプルコードを参考に試行錯誤した結果をチュートリアルとしてメモっとく。

こんなのができあがるので Play を勉強し始めたくらいの人でも Slick と連携する雰囲気がつかめるかと思う。

![play-slick-app-home.png](https://qiita-image-store.s3.amazonaws.com/0/67627/5cb6681d-9578-00db-5d45-8b2cfae7041d.png)

なお時間がある人にはビズリーチさんのハンズオンがオススメ。

https://github.com/bizreach/play2-hands-on

# 開発環境を準備する

まずは開発環境を準備しよう。必要なものは下記の2つだ。

* [Java SE Development Kit 8 (JDK 8)](http://www.oracle.com/technetwork/java/javase/downloads/)
* [Typesafe Activator](http://www.lightbend.com/activator/download)

Play 2.5 を使用するには JDK 8 が必要になるので JDK 8 をインストールする。ターミナルで下記のコマンドを実行して Java のバージョンが 1.8 以降であることを確認しよう。

```shell-session
$ java -version
java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)
```

さらに Play 2.5 のプロジェクトを作成するために Activator が必要なのでインストールする。macOS であれば Homebrew で下記のようにインストールできる。

```shell-session
$ brew install typesafe-activator
```

ターミナルで下記のコマンドを実行して Activator のヘルプが表示されることを確認しよう。

```shell-session
$ activator -h
Usage: activator [options]

  Command:
  ui                 Start the Activator UI
.
.
.
```

これで開発環境の準備は完了だ。

# Slick Plugin for Play のサンプルコードを実行してみる

この記事では Slick Plugin for Play のサンプルコードを参考にほぼゼロからプロジェクトを構築していくが、できあがるものはサンプルコードとほぼ同じになる。完成するものをイメージできた方がいいと思うので、それを最初に実行してみることにしよう。

ターミナルで下記のコマンドを実行してみよう。なお `activator` の初回実行時に依存性が解決されるので実行に数分かかると思う。

```shell-session
$ git clone https://github.com/playframework/play-slick
$ cd play-slick
$ activator
> project basic-sample
[basic-sample] $ run
.
.
.

--- (Running the application, auto-reloading is enabled) ---

[info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)
```

サーバーが起動したら http://localhost:9000 にアクセスしてみよう。データベースのマイグレーションが必要なため下記のようなエラーが表示される。

![play-slick-basic-sample-evolution.png](https://qiita-image-store.s3.amazonaws.com/0/67627/4a8d970f-2d59-cf05-0277-a8f05e5974dd.png)

`Apply this script now!` という赤いボタンをクリックしてデータベースをマイグレーションしよう。そうすると下記のような画面が表示される。

![play-slick-basic-sample-home.png](https://qiita-image-store.s3.amazonaws.com/0/67627/0bb03584-e0f1-37d1-b044-5e8f8fbe30e5.png)

猫と犬を登録できるフォームがそれぞれ表示される。どちらも名前と色を入力して登録すると下に一覧されるようになる。

# 新規 Play プロジェクトを作成する

さてサンプルコードをスクラッチから作っていこう。

下記のコマンドで新規の Play プロジェクトが作成できる。ちなみに `play-slick-app` がプロジェクト名になる。

```shell-session
$ activator new play-slick-app play-scala
```

この状態でサーバーを起動してみよう。この実行も数分かかるので気長に待とう。

```shell-session
$ cd play-slick-app
$ activator run
.
.
.

--- (Running the application, auto-reloading is enabled) ---

[info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)
```

サーバーが起動したら http://localhost:9000 にアクセスして下記の画面が表示されることを確認しよう。

![play-slick-app-initial.png](https://qiita-image-store.s3.amazonaws.com/0/67627/ee0e4398-f663-5a34-e285-6cb04023cc67.png)

# Slick Plugin for Play を追加する

Play で Slick を使用するためのプラグインの依存性を `build.sbt` に追記する。

```diff:build.sbt
@@ -7,9 +7,11 @@ lazy val root = (project in file(".")).enablePlugins(PlayScala)
 scalaVersion := "2.11.7"
 
 libraryDependencies ++= Seq(
-  jdbc,
   cache,
   ws,
-  "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test
+  "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test,
+  "com.typesafe.play" %% "play-slick" % "2.0.0",
+  "com.typesafe.play" %% "play-slick-evolutions" % "2.0.0",
+  "com.h2database" % "h2" % "1.3.176"
 )
```

この変更の意味は下記のとおり。今回は準備を簡単にするために H2 データベースをインメモリで使用する。

* `jdbc` は `play-slick` と衝突するので削除
* `play-slick` はプラグイン本体
* `play-slick-evolutions` はデータベースのマイグレーション管理
* `h2` はデータベースドライバー

この状態で `activator run` を実行してエラーが表示されないことを確認しておこう。

# Evolutions でデータベースのテーブルを作成する

下記の内容で `conf/evolutions/default/1.sql` という名前のファイルを作成する。この記事ではサンプルコードの猫だけを実装することにする。

```sql:conf/evolutions/default/1.sql
# --- !Ups

CREATE TABLE "CAT" (
    "NAME" VARCHAR NOT NULL PRIMARY KEY,
    "COLOR" VARCHAR NOT NULL
);

# --- !Downs

DROP TABLE "CAT";
```

この状態で下記のコマンドを実行する。

```shell-session
$ activator
[play-slick-app] $ h2-browser
[play-slick-app] $ run
```

`h2-browser` を実行したタイミングで下記のような H2 ブラウザの画面が開かれる。

![h2-browser-login.png](https://qiita-image-store.s3.amazonaws.com/0/67627/76d603e1-b072-9f04-ca59-c63981349825.png)

`JDBC URL` を `jdbc:h2:mem:play` に設定して Connect をクリックすると下記のような画面に遷移する。

![h2-browser-tables-1.png](https://qiita-image-store.s3.amazonaws.com/0/67627/3c149889-f9e1-52e7-208c-49547dd19dfa.png)

まだデータベースにはテーブルが作成されていないことがわかる。この状態で `run` を実行して、http://localhost:9000 にアクセスするとマイグレーションが必要というメッセージが出るはずだ。サンプルコードを実行したときのように `Apply this script now!` という赤いボタンをクリックしてデータベースをマイグレーションしておこう。

マイグレーションが完了したら H2 ブラウザのウィンドウに戻ってリロードする。そうすると下記のように `CAT` のテーブルが作成されていることがわかる。

![h2-browser-tables-2.png](https://qiita-image-store.s3.amazonaws.com/0/67627/8f5d43e4-31cd-8f3e-01b5-77fbd41bae26.png)

# DAO と DTO を定義する

まずは猫を表現する DTO をケースクラスを使用して定義する。

```scala:app/models/Models.scala
package models

case class Cat(name: String, color: String)
```

猫テーブルにアクセスするための DAO を定義する。サンプルコードをまるっとコピーしてきたものがこれだ。

```scala:app/dao/CatDAO.scala
package dao

import scala.concurrent.Future

import javax.inject.Inject
import models.Cat
import play.api.db.slick.DatabaseConfigProvider
import play.api.db.slick.HasDatabaseConfigProvider
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import slick.driver.JdbcProfile

class CatDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
  import driver.api._

  private val Cats = TableQuery[CatsTable]

  def all(): Future[Seq[Cat]] = db.run(Cats.result)

  def insert(cat: Cat): Future[Unit] = db.run(Cats += cat).map { _ => () }

  private class CatsTable(tag: Tag) extends Table[Cat](tag, "CAT") {

    def name = column[String]("NAME", O.PrimaryKey)
    def color = column[String]("COLOR")

    def * = (name, color) <> (Cat.tupled, Cat.unapply _)
  }
}
```

DAO のクラス内に Slick におけるテーブル定義がプライベートクラスとして記述されている。`all` メソッドと `insert` メソッドの内容は [Slick の Getting Started](http://slick.lightbend.com/doc/3.1.1/gettingstarted.html) を見ると詳細を理解できるかと。

この中で難解なのはインジェクションされる `dbConfigProvider` と DAO が継承している `HasDatabaseConfigProvider[JdbcProfile]` と突然登場する `db` だろう。Slick の Getting Started だと `Database.forConfig()` で作成して `db` に束縛しているのだけど Play と Slick でのサンプルでは異なる。

端的に言えば下記のようなことが行われていて、`db` 自体は Getting Started と同様に利用できるものなのだ。

1. データベース設定を提供する `DatabaseConfigProvider` がインジェクションされる
2. `HasDatabaseConfigProvider` でデータベース設定が取得される
3. `HasDatabaseConfigProvider` の親トレイトの `HasDatabaseConfig` でデータベース設定から `db` が定義される

これで Slick のドキュメント眺めつつ DAO を書き始められるんじゃないだろうか。Slick の学習はそれはそれで大変だけどね……。

# 猫をデータベースに追加してみる

PlayConsole から猫をデータベースに追加してみることにする。マイグレーションを自動実行するために下記のようなコマンドで Activator を起動してコンソールにアクセスする。

```shell-session
$ activator console -Dplay.evolutions.db.default.autoApply=true
```

[PlayConsole の Launch the interactive console](https://www.playframework.com/documentation/2.5.x/PlayConsole#launch-the-interactive-console) を参考に Play アプリケーションをコンソール上でスタートさせる。

```shell-session
> import play.api._
> val env = Environment(new java.io.File("."), this.getClass.getClassLoader, Mode.Dev)
> val context = ApplicationLoader.createContext(env)
> val loader = ApplicationLoader(context)
> val app = loader.load(context)
> Play.start(app)
```

猫の DAO と DTO をインポートする。そして CatDAO のインスタンスを取得して、猫を一匹追加してみる。このあたりの手順はサンプルコードのテストコードを参考にした。

```shell-session
> import dao.CatDAO
> import models.Cat
> import scala.concurrent.Await
> import scala.concurrent.duration.DurationInt
> val app2dao = Application.instanceCache[CatDAO]
> val dao = app2dao(app)
> val cat = new Cat("Tama", "White")
> Await.result(dao.insert(cat), 1.second)
> Await.result(dao.all, 1.second)
res2: Seq[models.Cat] = Vector(Cat(Tama,White))
```

PlayConsole からの追加は少し面倒だけど、ここまでの作業がうまくいってることを確認するためにやってみた。

# ビューとコントローラーを定義する

さてビューとコントローラーを定義しよう。といってもビューとコントローラーについては Play の話になってくる。このあたりは Play のドキュメントをつまみ食いしながら理解できるかと思うので駆け足でいく。基本はサンプルコードからのコピペで猫の部分だけ実装している。

まずはルーティング。フォームの POST 先を作成する。サンプルの URL は RESTful じゃないけど目をつむろう。

```diff:conf/routes
@@ -4,6 +4,7 @@
 
 # An example controller showing a sample home page
 GET     /                           controllers.HomeController.index
+POST    /insert/cat                 controllers.HomeController.insertCat
 # An example controller showing how to use dependency injection
 GET     /count                      controllers.CountController.count
 # An example controller showing how to write asynchronous code
```

次にコントローラー。`HomeController.scala` を下記のように編集する。`CatDAO` はインジェクションするので `class` の行を変更するのを忘れないように。

```diff:controllers/HomeController.scala
@@ -4,12 +4,19 @@ import javax.inject._
 import play.api._
 import play.api.mvc._
 
+import dao.CatDAO
+import models.Cat
+import play.api.data.Form
+import play.api.data.Forms.mapping
+import play.api.data.Forms.text
+import play.api.libs.concurrent.Execution.Implicits.defaultContext
+
 /**
  * This controller creates an `Action` to handle HTTP requests to the
  * application's home page.
  */
 @Singleton
-class HomeController @Inject() extends Controller {
+class HomeController @Inject() (catDao: CatDAO) extends Controller {
 
   /**
    * Create an Action to render an HTML page with a welcome message.
@@ -17,8 +24,22 @@ class HomeController @Inject() extends Controller {
    * will be called when the application receives a `GET` request with
    * a path of `/`.
    */
-  def index = Action {
-    Ok(views.html.index("Your new application is ready."))
+  def index = Action.async {
+    catDao.all().map {
+      cats => Ok(views.html.index(cats))
+    }
+  }
+
+  def insertCat = Action.async { implicit request =>
+    val cat: Cat = catForm.bindFromRequest.get
+    catDao.insert(cat).map(_ => Redirect(routes.HomeController.index))
   }
 
+  val catForm = Form(
+    mapping(
+      "name" -> text(),
+      "color" -> text()
+    )(Cat.apply)(Cat.unapply)
+  )
+
 }
```

次にビュー。`index.scala.html` を下記の内容に置換する。ここは理解しやすい内容かと思う。

```scala:views/index.scala.html
@(cats: Seq[Cat])

@main("Cat database") {
<div>
  <div id="cats">
    <h2>Insert a kitty cat here:</h2>

    <form action="/insert/cat" method="POST">
      <input name="name" type="text" placeholder="name your feline friend"/>
      <input name="color" type="text" placeholder="enter the color of this kitty cat"/>
      <input type="submit"/>
    </form>

    <h2>Previously inserted cats:</h2>
    <table>
      <tr><th>Name</th><th>Color</th></tr>
      @for(c <- cats){ 
      <tr><td>@c.name</td><td>@c.color</td></tr>
      }
    </table>
  </div>
</div>
}
```

# プロジェクトを実行する

起動して http://localhost:9000 にアクセスしよう。

```shell-session
$ activator run -Dplay.evolutions.db.default.autoApply=true
```

下記のような画面が表示されて、フォームから猫を追加できるはずだ。

![play-slick-app-home.png](https://qiita-image-store.s3.amazonaws.com/0/67627/5cb6681d-9578-00db-5d45-8b2cfae7041d.png)

---

この記事では Slick Plugin for Play をネタに Play Framework と Slick の連携をスクラッチから書く手順をチュートリアルっぽく書いてみた。僕自身 Slick を Play で動作させることすらできずに一度挫折しているので、Play を始めた人のお役に立てれば幸いだ。

# 参考文献

https://www.playframework.com/documentation/2.5.x/PlayConsole
https://www.playframework.com/documentation/2.5.x/PlaySlick
https://www.playframework.com/documentation/2.5.x/Developing-with-the-H2-Database
http://slick.lightbend.com/doc/3.1.1/
https://github.com/playframework/play-slick
https://github.com/bizreach/play2-hands-on/