3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-08-12

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._

// ...

この extendsimport がないと↓みたいなコンパイルエラーが出る。

Error: value === is not a member of Tables.profile.simple.Column[String]

参考

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?