Skip to content

Instantly share code, notes, and snippets.

@dragisak
Last active March 18, 2018 21:01
Show Gist options
  • Save dragisak/765af7a841cc7d4e3cae to your computer and use it in GitHub Desktop.
Save dragisak/765af7a841cc7d4e3cae to your computer and use it in GitHub Desktop.
Composing Slick tables as traits in Play!
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