Slick のコードを生成する sbt プラグインを作りました
Slick コードを生成のイブラリがあり、公式のほうにいくつか使い方のサンプルがありますが、毎回コピペするのもなあと思って sbt プラグインにしました。今のところ Slick 2.1.0 に依存しています。Slick 3.0.0 が出たらそれ用のバージョンも出そうと思います。
インストールは plugins.sbt に addSbtPlugin を加えた上で、コード生成に使う JDBC ドライバを追加します。
// plugins.sbt addSbtPlugin("com.github.tototoshi" % "sbt-slick-codegen" % "0.1.0") // Database driver // For example, when you are using PostgreSQL libraryDependencies += "org.postgresql" % "postgresql" % "9.4-1201-jdbc41
build.sbt の設定は以下のような感じです。
// build.sbt import scala.slick.codegen.SourceCodeGenerator import scala.slick.{ model => m } // required slickCodegenSettings // required // Register codegen hook sourceGenerators in Compile <+= slickCodegen // required slickCodegenDatabaseUrl := "jdbc:postgresql://localhost/example" // required slickCodegenDatabaseUser := "dbuser" // required slickCodegenDatabasePassword := "dbpassword" // required (If not set, postgresql driver is choosen) slickCodegenDriver := scala.slick.driver.PostgresDriver // required (If not set, postgresql driver is choosen) slickCodegenJdbcDriver := "org.postgresql.Driver" // optional but maybe you want slickCodegenOutputPackage := "com.example.models" // optional, pass your own custom source code generator slickCodegenCodeGenerator := { (model: m.Model) => new SourceCodeGenerator(model) } // optional // For example, to exclude flyway's schema_version table from the target of codegen slickCodegenExcludedTables in Compile := Seq("schema_version")
sourceGenerators in Compile
に加えることでコンパイルの前にコード生成が走ります。この設定では毎回コード生成が走りますが、毎回同じファイルが生成されるため、再コンパイルされることはありません。コード生成自体の時間が気になる場合は slickCodegen
タスクにキャッシュを入れればよいと思います。sbt の FileFunction.cached
とか使えば簡単に実装できると思います。
デフォルトでは Slick の SourceCodeGenerator
によりソースコード生成が行われますが、以前このブログにも書きましたが、java.sql.Timestamp
じゃなくて joda-time を使いたい、などということがあると思います。そのときは slickCodegenCodeGenerator
にカスタマイズした SourceCodeGenerator
を設定します。
slickCodegenCodeGenerator := { (model: m.Model) => new SourceCodeGenerator(model) { override def code = "import com.github.tototoshi.slick.PostgresJodaSupport._\n" + "import org.joda.time.DateTime\n" + super.code override def Table = new Table(_) { override def Column = new Column(_) { override def rawType = model.tpe match { case "java.sql.Timestamp" => "DateTime" case _ => super.rawType } } } } }
データベースマイグレーションと共存させる
本編ここから。Slick に限った話ではないですが、すでにデータベースにあるテーブルを使ってコード生成を行うという仕組みは、データベースマイグレーションツールとの相性が非常に悪いです。play-evolutions や play-flyway は Play アプリケーションの起動時にデータベースマイグレーションを行うため、コード生成とマイグレーションがデッドロックしてどうしようもありません。
今のところは dev-mode のデータベースマイグレーションは play-flyway ではなく、Flyway の sbt プラグインを使うことで回避しています。テストのときなどの Slick のコード生成が終わった後では play-flyway も使えるようになります。ちょっとダサい気がしているけれど、追加する設定も少ないし、まあ良いかという感じです。sbt ではなく Flyway のコマンドラインツールを使うというのでも良いと思います。
困ったことに Flyway の sbt プラグインを使ったからといってこの問題とさよならできるわけではありませんでした。実はこのプラグインもコンパイルの後にマイグレーションを走らせる作りになっているため、Flyway 用に別の空プロジェクトを使って、そこでマイグレーションを行う、というようにしました。flyway
という空プロジェクトを作ったら、マイグレーションは flyway/flywayMigrate
というコマンドで実行できます。
設定を見てもらうのが早いと思うので、サンプルプロジェクトにまとめて置きました。
回避に回避を重ねましたが、最終的な設定としては一応理解可能なレベルにまとめられたのではないかと思います。最初はサブプロジェクトで FakeApplication を使って無理やり Play.start してマイグレーションを走らせて...みたいなことをしていた(意味わかるでしょうか)のですがそれよりはずっと良いと思います。