Slick (play-slick) でクエリの共有

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アクセス

基本形はこんな感じ

```scala
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` とかで切り出して使い回す。

```scala
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` に切り出してミックスイン

```scala
/*
 * 切り出したクエリ
 */
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` のこの部分

```scala
trait FooQuery
  extends HasDatabaseConfigProvider[JdbcProfile] {
  import driver.api._

// ...
```

この `extends` と `import` がないと↓みたいなコンパイルエラーが出る。
```
Error: value === is not a member of Tables.profile.simple.Column[String]
```

## 参考

- [Slick 2.0.2 Error: value === is not a member of Tables.profile.simple.Column[String]](http://stackoverflow.com/questions/23721583/slick-2-0-2-error-value-is-not-a-member-of-tables-profile-simple-columnstr)
- [Slickで共通のクエリを定義する方法](http://qiita.com/mikamix/items/9545255ca326fe0c967b)
- [PlaySlickで使ってみるSlick3.1によるDB操作](http://catcher-in-the-tech.net/2333/)