-
-
Save stp-che/e5b1367f6ee4e21b2a32f09647113024 to your computer and use it in GitHub Desktop.
Composing Slick tables as traits in Play!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.inject.{Inject, Singleton} | |
import play.api.db.slick.DatabaseConfigProvider | |
import slick.backend.DatabaseConfig | |
import slick.driver.JdbcProfile | |
import scala.concurrent.Future | |
/* | |
* The problem we are trying to solve here is how to have one trait per Slick table definition. | |
* That will alow us to mix in only necessary tables with our repository classes. | |
* | |
* Main challenge is how to use Play's injected DatabaseConfigProvider with our traits. | |
*/ | |
/** | |
* This trait allows us to force presence of implicit dbConfig in our repository classes | |
*/ | |
trait SlickRepository { | |
implicit protected def dbConfig: DatabaseConfig[JdbcProfile] | |
/** | |
* This can get nasty. | |
* | |
* It has to be lazy or you end up with runtime NullPointerException because of trait initialization order | |
* and it has to be val because of import driver.api._ | |
* | |
* It's final to prevent anyone from overriding it downstream. | |
*/ | |
protected lazy final val driver = dbConfig.driver | |
} | |
case class Org(id: Long, name: String) | |
case class User(id: Long, orgId: Long, name: String) | |
/** | |
* Isolates Slick table definition into a trait. | |
* | |
* No more huge source files with 10s or 100s of tables ! | |
*/ | |
trait UserTable { | |
this: SlickRepository => // has to be mixed in with SlickRepository | |
import driver.api._ | |
// protected. don't leak internal implementation details | |
protected class Users(tag: Tag) extends Table[User](tag, "users") { | |
def id = column[Long] ("id", O.PrimaryKey) | |
def orgId = column[Long] ("org_id") | |
def name = column[String] ("name") | |
def * = (id, orgId, name) <> (User.tupled, User.unapply) | |
} | |
protected val users = TableQuery[Users] | |
} | |
trait OrgTable { | |
this: SlickRepository => | |
// has to be mixed in with SlickRepository | |
import driver.api._ | |
// protected so we don't leak internal details | |
protected class Orgs(tag: Tag) extends Table[Org](tag, "orgs") { | |
def id = column[Long] ("id", O.PrimaryKey) | |
def name = column[String] ("name") | |
def * = (id, name) <> (Org.tupled, Org.unapply) | |
} | |
protected val orgs = TableQuery[Orgs] | |
} | |
trait UserRepository { | |
def getOrgUsers(orgName: String): Future[Seq[User]] | |
} | |
/** | |
* Finally, wire everything up. | |
*/ | |
@Singleton | |
class UserRepositorySlick @Inject()(dbConfigProvider: DatabaseConfigProvider) | |
extends UserRepository | |
with SlickRepository with UserTable with OrgTable { // <-- reads nicely ! | |
// it's final to prevent anyone from overriding it | |
// it's implicit so that all the implicit column mappers can use it (not shown) | |
override implicit protected final val dbConfig = dbConfigProvider.get[JdbcProfile] | |
import dbConfig.driver.api._ | |
override def getOrgUsers(orgName: String): Future[Seq[User]] = dbConfig.db.run { | |
val q = for { | |
org <- orgs.filter(_.name.like(orgName)) | |
(_, user) <- orgs join users on (_.id === _.orgId) | |
} yield user | |
q.sortBy(_.name).result | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment