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 というコマンドで実行できます。

設定を見てもらうのが早いと思うので、サンプルプロジェクトにまとめて置きました。

sbt-slick-codegen-example/build.sbt at 7e14ae7e47539a3faa2556620a4afdce716261bf · tototoshi/sbt-slick-codegen-example · GitHub

回避に回避を重ねましたが、最終的な設定としては一応理解可能なレベルにまとめられたのではないかと思います。最初はサブプロジェクトで FakeApplication を使って無理やり Play.start してマイグレーションを走らせて...みたいなことをしていた(意味わかるでしょうか)のですがそれよりはずっと良いと思います。

退職しました

ピクシブを退職しました。 主に健康上の理由です。 (在宅勤務もさせてもらって、おかげさまで最近やっとよくなってはきましたが。) 退職はしましたが、4月からはフリーランスっぽい感じで仕事を続けます。

ピクシブに転職したのは、なんか楽しそうと思ったからなのですが、 実際とても楽しく過ごすことができました。

// TODO ここにいいことを書く

仕事はバックエンドの改善、課金周り、画像のアップロード、ストレージの移行、APIの移行などをやっていました。1年半で 99784 行のコードを追加し、251930 行のコードを削除しました。

今後は業務委託という形で今の仕事を継続する予定ですので、 次の WEB+DB PRESS で私がピクシブの連載を担当していても不思議に思わないでください。

基本リモートでよければ、他の仕事を受けることも可能です。 技術要素にはこだわらないですが、Play/Scala が得意分野です。 Play 導入してみたけどよくわからんぞなどとお困りでしたら呼んでください。 仕事がなければアルパカ牧場でも始めようと思います。

ピクシブのみなさん本当にありがとうございました。4月からもよろしくお願いします。

個人/社内 Maven Repository を作る

オープンソースScala ライブラリは sonatype を経由して Maven Central にデプロイするのが定番となっています。Maven Central のアカウントを持っていない、とかクローズドにしておきたい、などの理由から自前で Maven リポジトリを持ちたくなることがあるでしょう。

Maven リポジトリはなんとなくセットアップが面倒そうに思われていそうですが、実際には Web サーバ上にルールに従ってファイルが配置されているだけです。ArtifactoryNexus などの管理ツールもありますが、それらをインストールしなくとも簡単にセットアップが可能です。

例えば、gh-pages をリポジトリにしている人はたまに見かけます。自分も Sonatype のアカウントを作る前はそうしていました。それから svn リポジトリに jar を突っ込み、それを web 上に公開している人もいます。そういう風にリポジトリを公開するには、まず自分のローカルに Maven リポジトリを作り、それをアップロードする、という手順を踏みます。

自分のローカルにリポジトリを作るのがめんどうな場合は、WebDAV や scp を利用して、直接 Web 上のリポジトリにライブラリをデプロイすることも可能です。今回は WebDAV を使うことにしました。

まずは Web サーバの設定です。これは簡単で、WebDAV を有効にするだけです。Apache であれば次のような設定となります。 http://mvnrepoリポジトリの URL です。

<VirtualHost *:80>
  Servername mvnrepo
  DocumentRoot /var/www/mvnrepo
  DavLockDB /var/lock/apache2/DavLock
  DAVMinTimeout 600
  <Location />
    DAV On
  </Location>
</VirtualHost>

リポジトリができたら、リリース用のディレクトリとスナップショット用のディレクトリを切りましょう。 例えば、リリース用のディレクトリは http://mvnrepo/releases, スナップショット用のディレクトリは http://mvnrepo/snapshots という風にします。

次に、build.sbt に設定を追加します。

// Maven スタイルでデプロイする
publishMavenStyle := true

// テストは含めない
publishArtifact in Test := false

// 他のリポジトリのライブラリに依存することを許容する設定
pomIncludeRepository := { _ => false }

// リポジトリの設定
// SNAPSHOT バージョンであれば、http://mvnrepo/snapshots に
// リリースバージョンであれば、http://mvnrepo/releases にデプロイする
publishTo := {
  val inhouse = "http://mvnrepo/"
  if (version.value.trim.endsWith("SNAPSHOT"))
    Some("inhouse snapshots" at inhouse + "snapshots")
  else
    Some("inhouse releases"  at inhouse + "releases")
}

他に pomExtra の設定などもありますが、Maven Central ではないのでなくてもデプロイできるので割愛します。

さて、これであとは publish コマンドを打つだけですが、一つ問題があります。 sbt の問題か、sbt が依存するライブラリの問題かはわかりませんが、デプロイをするとき、パスの途中のディレクトリを作ってくれないのです。たとえば com.example という group_id で hello というライブラリの scala 2.11.x 用のバージョン 0.1.0 をデプロイしようとすると、その配置ディレクトリは /releases/com/example/hello_2.11/0.1.0/ ですが、このディレクトリを作る処理をせず、いきなりファイルを配置しようとするため、エラーが発生してしまいます。

これを避けるためには、自分で WebDAV の MKCOL リクエストを送る必要があります。もしくは Apache ではなく nginx を使って、create_full_put_path の設定を on にします。

今回は諸事情により、Apache を使うことになったので、自前で MKCOL を打つことになりますが、そんなことはやってられないので、解決策を探したところ、webdav4sbt という sbt プラグインを見つけました。このプラグインをインストールすると、sbt が publish の前に MKCOL リクエストを送ってくれるようになります。

しかし残念ながらこのライブラリはメンテナンスされておらず、ダウンロードもできない状態だったので Fork しました。scala 2.11 対応させ、名前も sbt-automkcol という風に変えました。

https://github.com/tototoshi/sbt-automkcol

注: 以下、情報が古くなっているのでGitHubのREADMEを参照してください

project/plugins.sbt に

addSbtPlugin("com.github.tototoshi" % "sbt-automkcol" % "1.5.1")

build.sbt に

import com.github.tototoshi.sbt.automkcol.Plugin._

AutoMkcol.globalSettings

と設定してください。

Apache の認証情報は ~/.sbt/0.13/build.sbt に書きます。

credentials += Credentials("inhouse maven repository", "mvnrepo", "user", "password")

これで Apache の場合でもデプロイができるようになります。

Maven リポジトリのセットアップ方法を紹介しましたが、オープンソースのライブラリであれば sonatype のアカウントを手にいれるなどして、Maven Central へデプロイすることを強くお勧めします。野良リポジトリの多くはいつのまにか管理されなくなり、ライブラリがダウンロードできなくなっているというのをよく見かけるからです。今回 Fork した webdav4sbt もリポジトリがなくなっていました。その点 Maven Central にデプロイすれば削除できないのでライブラリが消えることもありませんし、sbt に resolver の設定を追加する必要もありません。

Flyway プラグインを Play 2.4 対応しました

Play 2.1-2.3 向けにサードパーティプラグインとして play-flyway を開発してきましたが、少し前に Flyway の GitHub Organization に加えてもらい、めでたく公式モジュールとなりました。groupId も com.github.tototoshi から org.flywaydb になりました。

Play 2.4 はまだ final がリリースされてないのでまだスナップショット版ですが、 今の最新である Play 2.4-M2 向けにビルドしたものをリリースしておきました。

build.sbt に

resolvers +=
  "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"

libraryDependencies +=
  "org.flywaydb" %% "flyway-play" % "2.0.0-SNAPSHOT"

と書いた上で、conf/play.plugins ではなく、conf/application.conf

play.modules.enabled += "org.flywaydb.play.PlayModule"

と書くと有効になります。そのほかは今までの play-flyway と同様です。

Ansible を使ってみた感想

ここ数日 Ansible を触ってみてた。 MoinMoin Wiki のセットアップとか試しにやってみた。

tototoshi/ansible-playbook-moinmoin · GitHub

Chef の代替というよりも Chef + Capistrano/Fabric という感じ。

  • インストールが楽。設定ファイルもほとんどなし。
  • デフォルトでできることが多い。ApacheBasic 認証設定とか PostgreSQL のユーザー作成とかまで最初っから使えるのが良い。
  • ドキュメントが充実しているしサンプルも併記してくれていて親切。
  • あまりハマらない。

シンプルで良いツールだと思った。

yaml なので簡単なことは簡単にかけるけれど、手続きっぽいことを書くのは当然つらい。 そこは仕方ないかというところ。まあ数日使った程度ではそこまで困ることはなかった。

シェルスクリプトでは手続きを書くのに対し、 こういうツールでは冪等性うんぬんってやつでどんな状態に収束するかを書くので、 yaml で宣言的に書くほうが向いている(という都合の良い見方)。

あと一つ一つの記述は宣言的だけど、 それでいて処理は上からべたーっと順番に行われるのはわかりやすい。

うまく動いてくれないなーって思ったのが handlers。 難しいものではないんだけれど、試行錯誤しているうちに間接的な原因で意図通りに発火してくれないことがある。notify 書き忘れとかもあるし普通に最後に明示的に reload,restart とかしたほうがシンプルかも。

Chef はいろいろめんどくせーと思ってやらなかったけど Ansible ならイケると思ったし、 おいしいところだけ使うで十分得られるものが多そう。 簡単なのでまだ試してない人も一度触ってみると良いんじゃないかな。

IntelliJ IDEA を無料で使う方法

IntelliJ IDEA は Community Edition であれば無料で使えますが、 Ultimate Edition になると大体初期2万+維持費1万/年くらいの課金をする必要があります。

機能比較

これをどう見るかは人によると思いますが、自分は Scala はほとんど Emacs で書いてしまって、たまに気分で IntelliJ IDEA を使うくらいのノリなので少し高く感じています。

しかし、実は Ultimate Edition をタダで使う方法が存在します。Open Source License というヤツです。

IntelliJ IDEA 14.x Open Source License

Open Source License はオープンソースのプロジェクトの開発に使用できるライセンスです。ライセンスの発行には審査が必要です。 条件は、

  • プロジェクトのリーダー、または常にコミットしていること
  • プロジェクトがオープンソースの定義を満たしていること
  • 資金援助などを受けていないこと
  • アクティブに開発されていること
  • コミュニティがアクティブであること
  • ウェブサイトを持っていること
  • 定期的にリリースがされていること

と、一見厳しそうですが、コミュニティとかウェブサイトがどうとかは GitHubリポジトリや issues がその役目を果たしますし、規模とかはあまり関係なく、真面目に開発してる感さえあれば大丈夫だと思います。

というわけで、今回は tototoshi/scala-csv で申請してライセンスを発行してもらえました。ライセンスの発行には大体数日必要らしいですが、以前 PhpStorm のライセンスを tototoshi/staticmock に対して発行してもらったときは1ヶ月くらいかかった(絶対忘れてただろ)ので気長に待ちましょう。

Open Source License の有効期間は1年で、更新のときは期限切れの直前にメールでお願いする必要があるようです。

というわけで、自信のない人もとりあえず申請してみればよいと思います。JetBrains もそれなりに儲けてそうだし皆さんがタダで使っても潰れないでしょう。

追記: はてぶコメントで補足してくれてる方がいますが、仕事には使えないので会社に Commercial License を買ってもらいましょう

build.sbt の変更を検知する sbt プラグインを作りました

git でブランチを切り替えたらうまくビルドできなくなって困ったけど、build.sbt が変わっているのに sbt の reload をするのを忘れていただけだった、ということがたまに起こります。これを防ぐために、.sbt や project/.scala が変更されていたら警告を表示する sbt プラグインを作りました。

tototoshi/sbt-build-files-watcher

作りました、というかよしださんの https://gist.github.com/xuwei-k/6278769 をだいたいパクった感じです。

インストールはプロジェクトごとに設定するよりはグローバルの設定にすると便利だと思います。

~/.sbt/0.13/plugins/build.sbt

addSbtPlugin("com.github.tototoshi" % "sbt-build-files-watcher" % "0.1.1")

~/.sbt/0.13/build.sbt

showMessageOnBuildFilesChanged

sbt-git などを使っている場合は showMessageOnBuildFilesChanged が sbt-git のshowCurrentGitBranch とぶつかるので messageOnBuildFilesChanged という関数を使って shellPrompt キーを設定します。

shellPrompt := { state =>
  messageOnBuildFilesChanged(state) + GitCommand.prompt(state)
}

これで .sbt や project/.scala を編集すると reload しろという警告が出るようになります。めでたし。

f:id:tototoshi:20150131000659p:plain