Slick でテーブル定義のコードを自動生成する

Slick 2系では experimental 扱いではありますが、コード生成の機能が含まれていて、 めんどうなテーブル定義のコードを自動生成することができます。ちょっとやってみましょう。

テスト用のテーブル、こんな感じです。

slickcodegenexample=# CREATE TABLE users (
  id serial PRIMARY KEY,
  first_name varchar(100) NOT NULL,
  last_name varchar(100) NOT NULL,
  birth_date timestamp NOT NULL
);

コード生成機能は slick 本体の jar ではなく、slick-codegen と言う jar に含まれています。使用しているデータベースに合わせた jdbc driver も必要です。それぞれ build.sbt の libraryDependencies に加えて下さい。

name := """slick-codegen-example"""

version := "1.0"

scalaVersion := "2.11.1"

libraryDependencies ++= Seq(
  "com.typesafe.slick" %% "slick" % "2.1.0",
  "com.typesafe.slick" %% "slick-codegen" % "2.1.0",
  "org.postgresql" % "postgresql" % "9.3-1102-jdbc41"
)

コード生成のためのコードは以下のような単純なもので、SourceCodeGenerator.main というメソッドを呼ぶだけです。 気をつけることは jdbc driver と slick のほうの driver を合わせることくらいでしょうか。今回は PostgreSQL を使っているので scala.slick.driver.PostgresDriver を選びます。

package com.example

import scala.slick.codegen.SourceCodeGenerator

object SlickCodegen {

  def main(args: Array[String]): Unit = {
    val slickDriver = "scala.slick.driver.PostgresDriver"
    val jdbcDriver = "org.postgresql.Driver"
    val url = "jdbc:postgresql://localhost/slickcodegenexample"
    val outputFolder = "src/main/scala"
    val pkg = "com.example.models"

    SourceCodeGenerator.main(
      Array(
        slickDriver,
        jdbcDriver,
        url,
        outputFolder,
        pkg
      )
    )
  }
}

実行すると、outPutFolder で指定した場所にコードが生成されます。 見てみるといつも書いてるようなコードですね。

package com.example.models

object Tables extends {
  val profile = scala.slick.driver.PostgresDriver
} with Tables

trait Tables {
  val profile: scala.slick.driver.JdbcProfile
  import profile.simple._
  import scala.slick.model.ForeignKeyAction
  import scala.slick.jdbc.{GetResult => GR}

  lazy val ddl = Users.ddl

  case class UsersRow(id: Int, firstName: String, lastName: String, birthDate: java.sql.Timestamp)

  implicit def GetResultUsersRow(implicit e0: GR[Int], e1: GR[String], e2: GR[java.sql.Timestamp]): GR[UsersRow] = GR{
    prs => import prs._
    UsersRow.tupled((<<[Int], <<[String], <<[String], <<[java.sql.Timestamp]))
  }

  class Users(_tableTag: Tag) extends Table[UsersRow](_tableTag, "users") {
    def * = (id, firstName, lastName, birthDate) <> (UsersRow.tupled, UsersRow.unapply)
    def ? = (id.?, firstName.?, lastName.?, birthDate.?).shaped.<>({r=>import r._; _1.map(_=> UsersRow.tupled((_1.get, _2.get, _3.get, _4.get)))}, (_:Any) =>  throw new Exception("Inserting into ? projection not supported."))
    val id: Column[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey)
    val firstName: Column[String] = column[String]("first_name", O.Length(100,varying=true))
    val lastName: Column[String] = column[String]("last_name", O.Length(100,varying=true))
    val birthDate: Column[java.sql.Timestamp] = column[java.sql.Timestamp]("birth_date")
  }

  lazy val Users = new TableQuery(tag => new Users(tag))
}

データベースの timestamp 型が java.sql.Timestamp 型にマッピングされています。java.sql.Timestamp 型では少々使いにくい。Java 8 日時 API or joda-time が良さそうと思いますが、そういえば私は slick-joda-mapper というのを作っていました。これで timestamp 型を org.joda.time.DateTimeマッピングさせましょう。

https://github.com/tototoshi/slick-joda-mapper

slick-codegen でのコード生成はこういう時のためにカスタマイズが可能になっています。まず先ほどの build.sbt に joda-time と slick-joda-mapper の依存性も加えます。

name := """slick-codegen-example"""

version := "1.0"

scalaVersion := "2.11.1"

libraryDependencies ++= Seq(
  "com.typesafe.slick" %% "slick" % "2.1.0",
  "com.typesafe.slick" %% "slick-codegen" % "2.1.0",
  "org.postgresql" % "postgresql" % "9.3-1102-jdbc41",
  "joda-time" % "joda-time" % "2.4",
  "com.github.tototoshi" %% "slick-joda-mapper" % "1.2.0"
)

コード生成のためのコードは次のようになります。 SourceCodeGenerater を継承して、"java.sql.Timestamp""DateTime" に変更しています。

import scala.slick.{model => m}
import scala.slick.codegen.SourceCodeGenerator
import scala.slick.driver.JdbcProfile

class CustomSourceCodeGenerator(model: m.Model) extends 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
        }
      }
    }
  }
}

object CodeGen extends App {

  val slickDriver = "scala.slick.driver.PostgresDriver"
  val jdbcDriver = "org.postgresql.Driver"
  val url = "jdbc:postgresql://127.0.0.1/slickcodegenexample"
  val outputFolder = "src/main/scala"
  val pkg = "com.example.models"

  val driver: JdbcProfile = scala.slick.driver.PostgresDriver

  val db = {
    driver.simple.Database.forURL(url, driver = jdbcDriver)
  }

  db.withSession { implicit session =>
    new CustomSourceCodeGenerator(driver.createModel()).writeToFile(slickDriver, outputFolder, pkg)
  }

}

生成されたコードを見ると、さっき java.sql.Timestamp だったところが DateTime に変わっていることがわかります。

package com.example.models

object Tables extends {
  val profile = scala.slick.driver.PostgresDriver
} with Tables

trait Tables {
  val profile: scala.slick.driver.JdbcProfile
  import profile.simple._
  import com.github.tototoshi.slick.PostgresJodaSupport._
  import org.joda.time.DateTime
  import scala.slick.model.ForeignKeyAction

  import scala.slick.jdbc.{GetResult => GR}

  lazy val ddl = Users.ddl

  case class UsersRow(id: Int, firstName: String, lastName: String, birthDate: DateTime)

  implicit def GetResultUsersRow(implicit e0: GR[Int], e1: GR[String], e2: GR[DateTime]): GR[UsersRow] = GR{
    prs => import prs._
    UsersRow.tupled((<<[Int], <<[String], <<[String], <<[DateTime]))
  }

  class Users(_tableTag: Tag) extends Table[UsersRow](_tableTag, "users") {
    def * = (id, firstName, lastName, birthDate) <> (UsersRow.tupled, UsersRow.unapply)
    def ? = (id.?, firstName.?, lastName.?, birthDate.?).shaped.<>({r=>import r._; _1.map(_=> UsersRow.tupled((_1.get, _2.get, _3.get, _4.get)))}, (_:Any) =>  throw new Exception("Inserting into ? projection not supported."))
    val id: Column[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey)
    val firstName: Column[String] = column[String]("first_name", O.Length(100,varying=true))
    val lastName: Column[String] = column[String]("last_name", O.Length(100,varying=true))
    val birthDate: Column[DateTime] = column[DateTime]("birth_date")
  }

  lazy val Users = new TableQuery(tag => new Users(tag))
}

slick-codegen は sbt に組み込んでコンパイル時にコード生成を行う、ということもでき、そのサンプルが https://github.com/slick/slick-codegen-example にあります。

まだ experimental なのでそこまでやるべきかは微妙なところですが、少なくともコード生成自体は普通に使えそうだなあと思いました。

ScalaJP の Gitter がオープンしました

ScalaJP のメーリングリストでも流れていますが、 Gitter という Github と連携して使えるチャットサービスを利用して、Scala の日本語チャットルームができました。(kawachi さんが作ったのかな?)すでに 60 人くらい参加しているようです。

https://gitter.im/scalajp/public

Gitter → Twitter

Gitter に常駐するのがだるい人用に Twitter bot を作りました。

押そう→

Gitter で発言すると

f:id:tototoshi:20141004134531p:plain

Twitter に流れます。

Gitter の無料プランは 2 週間しか履歴が保存されないのですが、 Twitter に全部垂れ流すことで解決されました。Twitter はストレージサービスだったんだ!すごい!!

Twitter → Gitter

bot を作ったら Gitter のメッセージじゃなくてこの bot に返信してしまいそうになったので、 いっそのことこの bot にリプライすることで、Twitter から Gitter に投稿できるようにしてしまえ!となりました。

↑ のツイートは

f:id:tototoshi:20141004134217p:plain

↑ こんな感じで Gitter に流れます。

これで Gitter ⇄ Twitter の双方向のやりとりが可能になりましたが、 もしかして Gitter いらなくて全部 Twitter で良かったのではって感じがしてきました!

ともあれ、なにか質問を投げるとどこからか人が湧いてきて回答してくれる便利サービスになっているのでご活用ください。無料です。

ScalaMatsuri の運営に参加した感想

ScalaMatsuri の運営やってみての感想を書いておきます。そろそろ振り返りのミーティングもあるし。 ScalaMatsuri は体調のせいでほぼ参加できなかったので感想ありません(悲)。

ScalaMatsuri の意義について

今年 400 人も集まってしまったのは、オダスキーパワーですが、とはいえもう Scala の認知度はそれなりに上がってきていて、あとは使いたい人は勝手に使い始める段階に来ています。普及活動を目的とした ScalaMatsuri はもう意味がないのかなと思いました。実際会場に来ていた人の中でも業務で使っている人が多かったようです。

お堅い SIer でも普及させたいとか「ラムダがわからないおっさんに使ってもらうには?」みたいなのは会社や個人個人の都合がでかすぎるし、ScalaMatsuri としてそこに切り込んでいく必要はない、というか無理があると思いました。

...というのは割とこじつけかもしれない。一番の問題は自分に「Scala を普及させたい!」という気持ちがさほどない...

個人的には ScalaMatsuri の意義は、とにかく年1回カンファレンスを開いて盛り上がってる感を演出したり、コミュニティに刺激を与えること。Scala の普及とかは刺激を受けた人が自発的に勉強会始める、とかに任せたいです。(なぜならめんどうだから)

英語について

ScalaMatsuri は海外との交流を目的の1つとしてはいますが、それは英語をしゃべることを意味しないと思っています。 ScalaMatsuri の運営の中では英語を推奨する声が多数ですけど、自分は英語には興味はなくもっと Scala のほうを楽しみたいです。 英語の壁を取り去りたいという思惑が強すぎて、逆に英語を意識するようなイベントとなっていることは残念です。

イベントとしては同時通訳(できれば日本語英語双方向の)があれば解決する話。くよくよするな金で解決しろ。

リモートからの運営参加

準備ミーティングは体調の都合上リモート参加が多かったのですが、それほど困ることはありませんでした。 「来れない人はリモート」ではなく「基本リモートで、来れる人は現地に」という建前にしてもっと地方から協力してくれる方を増やしたいです。

その他

カンファレンスでのユニバーサル・アクセスへ向けて | eed3si9n Scala祭と日本のScalaコミュニティ - scalaとか・・・

だいたい同意です。

全体として

運営として、どのようなイベントにしたいか、というのはあるにしても、それよりもカンファレンスを楽しんでもらうことを優先したいです。

あと健康は大事です!

退院しました

退院ヤッター!

普段あまり医療のお世話にならずニュースとかだけで医療に接していると、すごいなあ最近の医療は進歩しているなあと思うけれど、実際入院して見るとそうでもないですね。腸の状態とか内視鏡入れなくてもわかるようになってて欲しいとか思うけどそんなことはなかった。

病院内は医師看護師さんがバタバタ動いてて血を抜いたり点滴変えて回ったり老人あやしたりしてました。医療はなにかすごいマジカルな仕組みで動いてるとかではなく、マンパワー&エンジニアリングだなと思いました。

病院と同じように Web サービスもぱっと見すごそうでも、魔法ではなく中の人のがんばりによって支えられているわけで、病院に失望したというわけではなく、医療従事者の方々に仲間意識のようなものを感じるとともに、世の中そんなもんですよねって思ったということです。

んなことより寿司食いたい。

入院しています

9/1から入院しています。 各方面ご迷惑をおかけしております。申し訳ありません。

ゴールデンウィークのころからお腹の調子が悪く、腹痛と下痢、血便で困っていました。近所の病院(代々木病院)に通っていたのですが、なかなか改善せず、代々木病院も全然話を聞かないひどい医者だったので、見切りを付けて別の病院に行ったところ、一瞬で大学病院送りとなりました。

内視鏡検査の結果、大腸の始まりと終わりの部分、2/3くらいが炎症を起こしており、潰瘍ができていました。 薬をもらって経過観察となりましたが、なかなか回復せず、検査入院ということで入院し、潰瘍性大腸炎と診断されました。検査入院で入院したんですが、いつのまにか治療モードになっていて、いや治療と言っても薬飲んで寝てるだけなんですがまだ退院できてません…

潰瘍性大腸炎については各自ぐぐって、めんどくさそう…と思っていただければと思います。 私から提供できる有用情報は

  • 健康は大事
  • お腹痛い人は変なのが出てきたらとっとと病院に行け
  • 緊急でない入院は高額療養費制度を考えると月をまたがないほうが良い
  • 関東ITS健保は高額療養費制度に一部負担還元金なるものがついてオトクっぽい

といったところです。

入院って暇ですね...

YAPC 2日目に行ってきました

YAPC初めて行きました。

1日目も行きたかったんだけど、諸事情(2つ前のエントリ参照)により来週から入院することになってしまい、ドタバタしたため行けませんでした。2日目も午後から参加です。PHP のやつ聞きたかったけど残念。

一番良かったのは Github の Andy さんによる「Changing the tires on a moving car: a case study in upgrading legacy architecture」。自分も仕事で最近はずっと古いシステムからの移行をやっているので、どこも大変だな〜と共感できるだけでも良かったです。サービスが大きくなるにつれてアーキテクチャを変えて行くのは難しいけれど Web エンジニアとしてはやりがいのある仕事だと思います。

後から考えてみると Grit から Rugged への移行で RPC を監視しているという話がいまいちわからないような。。。静的な解析ではだめなんだろうか?

あとこのセッションは同時通訳のクオリティに驚きました。専門性が高いテーマでもできちゃうんですね。

「モバイルアプリとAPIのありかたを考える2014」とか「Mobile Application Development for Perl Mongers」とか見たせいか、全体として Android/iOS のモバイルに移る Web エンジニアが増えたなという印象が強く残りました。自分も Java は書けるんだしそろそろ Android くらいには手を出しとこうかという気分になりました。

「趣味開発のためのクラウド/VPS活用術」もよくある話ではあるけれど実際に使ってみた結果としての各サービスの比較はあまりないので参考になりました。どうでもいいけどスポンサーに Conoha 並んでるのに Conoha の話が全然出てこないのがドキドキしました。

悪かった点としては席が全然足りていなかったこと。4並列でやっていたら偏りが出て座れない部屋が出るのは当然としても足りなすぎでした。最後のほう2時間くらいはずっと立ちっぱなしになり、体調も悪かったので途中で帰りました。1300人以上の参加者に対して藤原洋記念ホールのキャパ500人は少なすぎると思います。

それから YAPC ASIA というイベント名なのに、壊れた自撮りグッズのことをチャイナクオリティと言ったおっさんには不快感を覚えました。フィリピンの女性がうんぬんも場所によったらアウトでしょうね。

主に体調が悪いせいであまり楽しめなかったけれど、トークのクオリティは全体的に高くて面白いし、エンジニアが大量にいるのはそれだけで面白いので来年は全参加したいです。

YAPC運営・スピーカー・参加者の皆様お疲れ様でした。

「メンテナンス大変なのでサービス閉じたいです」

Web サービスの会社にはイケイケなサービスの陰に隠れて、かつてイケイケだったけど今はイケテナイサービス、とか、ぶっちゃけ最初っからあんまりやる気なかった☆サービス、これどこで拾ってきちゃったのサービス、などなど、惰性で続いちゃってるようなサービスがごろごろあったりします。

そういうイケテナイサービス群はメンテナンスの手間を取らせる物なので、エンジニアとしては閉じてしまいたい。でも非エンジニアとしては閉じたくない。いや、本当は閉じたい!でもちょっとは儲かってるし、閉じるとわずかながら存在するユーザーからクレームくるし、連携先の企業とやりとりするのめんどいし、まあいろいろめんどうそう。

そこで、「イケテナイサービスにはできる限りメンテナンスの手間を払うな」という話がエンジニアのとこにきて「お前なーっ!そのメンテナンスの手間がなーっ!」となり、話がループを始めます。なぜこういう噛み合わない話になってしまうのか。

エンジニアがメンテナンスの大変さを説明するのに「フレームワークのバージョンアップとか大変なんですよー」とか言っても、非エンジニアは「ふーん、そうなんだ(無理にバージョンアップしなければ良いんじゃね?)」とか思ってそうです。自分エンジニアだからわからないけど多分そう。

エンジニアは「Webサービスは(言語・ライブラリ・関連しているサービスの影響などで)メンテナンスしないと壊れる。」などと思っているのに対して、非エンジニアはなんとなく「ほっとけば動いてそう。」と思ってる気がします。

なので、最近は「メンテナンス大変なんですよー」じゃなくて「ほっとくと壊れるんですよー」とかよく言ってます。なにか他にもっと気の利いた言い方あります?