アカウンティング・サース・ジャパン Advent Calendar 2016 の7日目なのだ。
新しめの Play Framework で Slick を使ってみようとするとサクッと写経できるチュートリアルがない。ということで Slick Plugin for Play のサンプルコードを参考に試行錯誤した結果をチュートリアルとしてメモっとく。
こんなのができあがるので Play を勉強し始めたくらいの人でも Slick と連携する雰囲気がつかめるかと思う。
なお時間がある人にはビズリーチさんのハンズオンがオススメ。
開発環境を準備する
まずは開発環境を準備しよう。必要なものは下記の2つだ。
Play 2.5 を使用するには JDK 8 が必要になるので JDK 8 をインストールする。ターミナルで下記のコマンドを実行して Java のバージョンが 1.8 以降であることを確認しよう。
$ 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 で下記のようにインストールできる。
$ brew install typesafe-activator
ターミナルで下記のコマンドを実行して Activator のヘルプが表示されることを確認しよう。
$ activator -h
Usage: activator [options]
Command:
ui Start the Activator UI
.
.
.
これで開発環境の準備は完了だ。
Slick Plugin for Play のサンプルコードを実行してみる
この記事では Slick Plugin for Play のサンプルコードを参考にほぼゼロからプロジェクトを構築していくが、できあがるものはサンプルコードとほぼ同じになる。完成するものをイメージできた方がいいと思うので、それを最初に実行してみることにしよう。
ターミナルで下記のコマンドを実行してみよう。なお activator
の初回実行時に依存性が解決されるので実行に数分かかると思う。
$ 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 にアクセスしてみよう。データベースのマイグレーションが必要なため下記のようなエラーが表示される。
Apply this script now!
という赤いボタンをクリックしてデータベースをマイグレーションしよう。そうすると下記のような画面が表示される。
猫と犬を登録できるフォームがそれぞれ表示される。どちらも名前と色を入力して登録すると下に一覧されるようになる。
新規 Play プロジェクトを作成する
さてサンプルコードをスクラッチから作っていこう。
下記のコマンドで新規の Play プロジェクトが作成できる。ちなみに play-slick-app
がプロジェクト名になる。
$ activator new play-slick-app play-scala
この状態でサーバーを起動してみよう。この実行も数分かかるので気長に待とう。
$ 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 にアクセスして下記の画面が表示されることを確認しよう。
Slick Plugin for Play を追加する
Play で Slick を使用するためのプラグインの依存性を 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
という名前のファイルを作成する。この記事ではサンプルコードの猫だけを実装することにする。
# --- !Ups
CREATE TABLE "CAT" (
"NAME" VARCHAR NOT NULL PRIMARY KEY,
"COLOR" VARCHAR NOT NULL
);
# --- !Downs
DROP TABLE "CAT";
この状態で下記のコマンドを実行する。
$ activator
[play-slick-app] $ h2-browser
[play-slick-app] $ run
h2-browser
を実行したタイミングで下記のような H2 ブラウザの画面が開かれる。
JDBC URL
を jdbc:h2:mem:play
に設定して Connect をクリックすると下記のような画面に遷移する。
まだデータベースにはテーブルが作成されていないことがわかる。この状態で run
を実行して、http://localhost:9000 にアクセスするとマイグレーションが必要というメッセージが出るはずだ。サンプルコードを実行したときのように Apply this script now!
という赤いボタンをクリックしてデータベースをマイグレーションしておこう。
マイグレーションが完了したら H2 ブラウザのウィンドウに戻ってリロードする。そうすると下記のように CAT
のテーブルが作成されていることがわかる。
DAO と DTO を定義する
まずは猫を表現する DTO をケースクラスを使用して定義する。
package models
case class Cat(name: String, color: String)
猫テーブルにアクセスするための DAO を定義する。サンプルコードをまるっとコピーしてきたものがこれだ。
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 を見ると詳細を理解できるかと。
この中で難解なのはインジェクションされる dbConfigProvider
と DAO が継承している HasDatabaseConfigProvider[JdbcProfile]
と突然登場する db
だろう。Slick の Getting Started だと Database.forConfig()
で作成して db
に束縛しているのだけど Play と Slick でのサンプルでは異なる。
端的に言えば下記のようなことが行われていて、db
自体は Getting Started と同様に利用できるものなのだ。
- データベース設定を提供する
DatabaseConfigProvider
がインジェクションされる -
HasDatabaseConfigProvider
でデータベース設定が取得される -
HasDatabaseConfigProvider
の親トレイトのHasDatabaseConfig
でデータベース設定からdb
が定義される
これで Slick のドキュメント眺めつつ DAO を書き始められるんじゃないだろうか。Slick の学習はそれはそれで大変だけどね……。
猫をデータベースに追加してみる
PlayConsole から猫をデータベースに追加してみることにする。マイグレーションを自動実行するために下記のようなコマンドで Activator を起動してコンソールにアクセスする。
$ activator console -Dplay.evolutions.db.default.autoApply=true
PlayConsole の Launch the interactive console を参考に Play アプリケーションをコンソール上でスタートさせる。
> 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 のインスタンスを取得して、猫を一匹追加してみる。このあたりの手順はサンプルコードのテストコードを参考にした。
> 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 じゃないけど目をつむろう。
@@ -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
の行を変更するのを忘れないように。
@@ -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
を下記の内容に置換する。ここは理解しやすい内容かと思う。
@(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 にアクセスしよう。
$ activator run -Dplay.evolutions.db.default.autoApply=true
下記のような画面が表示されて、フォームから猫を追加できるはずだ。
この記事では 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/