Slick(play-slick)でクエリを共有したい時のTips
環境
- Scala 2.11.6
- Play 2.4.4
- play-slick 1.1.0
- slick 3.1.0
Slick(play-slick)でDBアクセス
基本形はこんな感じ
package repositories
import javax.inject.Inject
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import com.google.inject.ImplementedBy
import models.Tables
import models.Tables._
import slick.driver.JdbcProfile
@ImplementedBy(classOf[FooRepository])
trait FooRepositoryLike
extends HasDatabaseConfigProvider[JdbcProfile] {
def findById(id: String): Future[Option[(FooRow, BarRow)]]
}
class FooRepository @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
extends FooRepositoryLike {
import driver.api._
def findById(id: String): Future[Option[(FooRow, BarRow)]] = {
val query = for {
f <- Foo if f.id === id.bind
b <- Bar if b.id === f.barId
// and more...
} yield (f, b)
db.run(query.result.headOption)
}
}
で、今回の主題であるクエリって呼んでるのが↑の val query = for {...
の部分
複雑なテーブル構成だとここが結構肥大化する。
さらに↑のfindById
と同じデータを取得する別のメソッドやクラスがあった場合、それぞれに同じクエリを実装するのは冗長。保守性に欠ける。ので共有したい。
同一クラス内で共有
同一クラス内であればクエリ部分だけ lazy val
とかで切り出して使い回す。
class FooRepository @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
extends FooRepositoryLike {
import driver.api._
/*
* 切り出したクエリ
*/
lazy val fooQuery = for {
f <- Foo if f.id === id.bind
b <- Bar if b.id === f.barId
// and more...
} yield (f, b)
def findById(id: String): Future[Option[(FooRow, BarRow)]] =
db.run(fooQuery.filter(_._1.id === id.bind).result.headOption)
def findAll(): Future[Seq[(FooRow, BarRow)]] =
db.run(fooQuery.sortBy(_._1.createAt.desc).result)
}
別クラスで共有
同じクエリを別クラスで使いたい場合、 trait
に切り出してミックスイン
/*
* 切り出したクエリ
*/
trait FooQuery
extends HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
lazy val fooQuery = for {
f <- Foo if f.id === id.bind
b <- Bar if b.id === f.barId
// and more...
} yield (f, b)
}
class FooRepository @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
extends FooRepositoryLike with FooQuery {
import driver.api._
def findById(id: String): Future[Option[(FooRow, BarRow)]] =
db.run(fooQuery.filter(_._1.id === id.bind).result.headOption)
def findAll(): Future[Seq[(FooRow, BarRow)]] =
db.run(fooQuery.sortBy(_._1.createAt.desc).result)
}
// ----------------
class BazRepository @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
extends BazRepositoryLike with FooQuery {
import driver.api._
def findById(id: String): Future[Option[(BazRow, FooRow, BarRow)]] = {
val query = for {
bz <- Baz if bz.id === id.bind
(f, b) <- fooQuery if f.id === bz.fooId
} yield (bz, f, b)
db.run(query.result.headOption)
}
}
ポイントは切り出した trait
のこの部分
trait FooQuery
extends HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
// ...
この extends
と import
がないと↓みたいなコンパイルエラーが出る。
Error: value === is not a member of Tables.profile.simple.Column[String]