play-slickを使ってみる

私はRDBMSが嫌いです。理由はこの記事に書いてあります。

そういう訳でEntity FrameworkのようにDBをほとんど意識せず直感的に使えるようなフレームワークが大好きです。Play FrameworkとSlickを組み合わせれば限りなく私の理想に近づくので本気で勉強してみたいと思います。そういうメモ。

サンプルプロジェクトのダウンロード

Typesafe Activatorを使ってplay-slick-quickstartをダウンロードしてみます。

app/models/Cat.scala

package models
import play.api.db.slick.Config.driver.simple._
case class Cat(name: String, color: String)
/* Table mapping
 */
class CatsTable(tag: Tag) extends Table[Cat](tag, "CAT") {
  def name = column[String]("name", O.PrimaryKey)
  def color = column[String]("color", O.NotNull)
  def * = (name, color) <> (Cat.tupled, Cat.unapply _)
}

このようにモデルを定義しておけば勝手にDB上にテーブルを作ってくれるそうです。変更すればちゃんと追従されるそうです。すごいね。

/app/controllers/Application.scala

//create an instance of the table
val cats = TableQuery[CatsTable]
...
def index = DBAction { implicit rs =>
 Ok(views.html.index(cats.list))
}

これだけでCatの全タプルが持ってこれるそうです。らっくちーん。

MySQLで動くようにしてみる

phpMyAdminを使いたいという理由だけでMySQLでの運用を目指しています。MySQLで動くように変更してみましょう。mysqlのドライバの依存性を追加します。するとsbtが勝手に解決してくれます。

/build.sbt

libraryDependencies ++= Seq(
  "org.webjars" %% "webjars-play" % "2.2.2",
  "mysql" % "mysql-connector-java" % "5.1.24", ←これ
  "com.typesafe.play" %% "play-slick" % "0.7.0"
)

次にDBのデフォルト設定を変更します。デフォルトではH2がインメモリで動いています。これを下記のように変更します。

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=com.mysql.jdbc.Driver
db.default.url="jdbc:mysql://localhost/scala"
db.default.user=scala
db.default.password=password

さすがにDBのユーザーやスキーマまでは勝手に作ってくれないので予め作っておきます。

実行する

http://localhost:9000にアクセスします。

スキーマ更新しろよハゲ!と怒られました。そんなに真っ赤にならなくても・・・。怖いので「Apply this script now!」を押します。

出ました。

動いてるね。

テーブルも行もできてます。これすごくね?

じゃあ今度はAgeという属性を追加し、これも記録できるようにスキーマを変更しましょう。

Cat.scala

package models
import play.api.db.slick.Config.driver.simple._
case class Cat(name: String, color: String, age: Int)
/* Table mapping */
class CatsTable(tag: Tag) extends Table[Cat](tag, "CAT") {
  def name = column[String]("name", O.PrimaryKey)
  def color = column[String]("color", O.NotNull)
  def age = column[Int]("age", O.Nullable)
  def * = (name, color, age) <> (Cat.tupled, Cat.unapply _)
}

Application.scala

....
val catForm = Form(
    mapping(
      "name" -> text(),
      "color" -> text(),
      "age" -> number()
    )(Cat.apply)(Cat.unapply)
  )
....

index.scala.html

...
  <form action="/insert" 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 name="age" type="text" placeholder="enter the age of this kitty cat"/>
    <input type="submit"/>
  </form>

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

実行してみます。また怒られました。そんなに真っ赤にならなくても・・・。

流すSQL文を見ればわかりますが、中に入っているデータまでは移行してくれないようですね。まあエスパーじゃないし無理か。

上手く動いた。こいつはラックち~んですね。