Slickには素のSQLを文字列で書く方法もあります。
http://slick.typesafe.com/doc/3.0.2/sql.html
これを使ってusersテーブルからデータを取得する処理を書いてみます。
試したのは、Scala2.11.6, Slick3.0.0です。
case class User(id: Int, email: String, name: String, age: Option[Int], address: Option[String])
def selectUsers: DBIO[Seq[User]] = sql"""
SELECT
id,
email,
name,
age,
address
FROM
users
""".as[User]
implicit def getUserResult = GetResult { r=> User(r.nextInt, r.nextString, r.nextString, r.nextInt, r.nextString) }
SQLの結果からオブジェクトをつくるのが getUserResult
メソッドなわけですが、r.nextInt
とかはJDBCのResultSetを彷彿とさせますね。
r
はPositionedResult
型のオブジェクトです。
r.nextInt
とかは、r.<<
と書くとちょっと楽になるようです。
implicit def getUserResult = GetResult { r => User(r.<<, r.<<, r.<<, r.<<, r.<<) }
ちょっと楽になったけど、r.<<
の数を気にするのが面倒です。他のSQLも書いてるとボイラープレート化しますし、どうにかならないかなぁと思ってマクロを作ってやってみました。
作ったマクロを使うと下のように、createObj
メソッドに生成したいケースクラスの型を型パラメータに、GetResult
内の関数で受け取るr
をパラメータに渡すだけでよくなります。r.<<
を何回も書かなくてよくなりました!
implicit def getUserResult = GetResult { r => createObj[User](r) }
r
を1回しか使ってないから_
を使えますね。
implicit def getUserResult = GetResult { createObj[User](_) }
マクロの実装はこんな感じです。
package mymacros
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
import slick.jdbc.PositionedResult
object MyMacro {
// マクロを利用する側から呼び出すメソッド
def createObj[A](r: PositionedResult): A = macro createObjImpl[A]
// マクロの実装
def createObjImpl[A: c.WeakTypeTag](c: blackbox.Context)(r: c.Expr[PositionedResult]) = {
import c.universe._
// ケースクラス名のシンボルを取得
val caseClassSym: c.universe.Symbol = c.weakTypeOf[A].typeSymbol
if (!caseClassSym.isClass || !caseClassSym.asClass.isCaseClass) c.abort(c.enclosingPosition, s"$caseClassSym is not a case class")
// ケースクラスのフィールドを取得
val fields = caseClassSym.typeSignature.decls.filter {
case x: TermSymbol => x.isVal && x.isCaseAccessor
case _ => false
}
// rr.<<, rr.<<・・・
val params = fields.map(_ => q"rr.<<")
// User(r.<<, r.<< ・・・) っていうコード
val code = q"""
val rr = $r
${caseClassSym.name.toTermName}(..$params)
"""
code
}
}
参考にさせていただきました。
- http://matsu-chara.hatenablog.com/entry/2015/06/21/110000
- http://docs.scala-lang.org/ja/overviews/macros/overview.html
- http://docs.scala-lang.org/ja/overviews/reflection/overview.html
- http://d.hatena.ne.jp/xuwei/20140831/1409462088
初めてマクロ作ってみたんですが、コンパイル時に動作するものだから事前にコンパイルしておく必要があるんですねー。sbtで自作マクロを使うのに参考にさせていただきました。