I have this base trait
trait MyBase {
type M
type T <: Table[M]
val query: TableQuery[T]
}
Where TableQuery
is scala.slick.lifted.TableQuery
My subclasses instantiate TableQuery
like so:
type M = Account
type T = AccountsTable
val query = TableQuery[T]
I'd like to instantiate the TableQuery
in the base trait, possibly by using a lazy val
, i.e.
lazy val query: TableQuery[T] = {
...
}
I've been playing around with reflection, but haven't had much luck.
If I understand correctly, what you want is to be able to extend
MyBase
by simply defining M
and T
but without having to explicitly instantiate the TableQuery
in each derived class.
Using reflection is not really an option because normally you use TableQuery.apply
for that (as in val query = TableQuery[MyTable]
), and this is implemented through a macro,
so you've got a "runtime vs compile-time" issue.
If you absolutely need MyBase
to be a trait (as opposed to a class), then I don't see any viable solution.
However if you can turn MyBase
into a class and turn M
and T
into type parameters (instead of abstract types), then there is at least one solution.
As I hinted in another related question (How to define generic type in Scala?), you can
define a type class (say TableQueryBuilder
) to capture the call to TableQuery.apply
(at the point where the concrete type is known) along with an implicit macro (say TableQueryBuilder.builderForTable
) to provide
an instance of this type class. You can then define a method (say TableQueryBuilder.build
) to actually instantiate the TableQuery
, which will just delegate to job to the type class.
// NOTE: tested with scala 2.11.0 & slick 3.0.0
import scala.reflect.macros.Context
import scala.language.experimental.macros
object TableQueryBuilderMacro {
def createBuilderImpl[T<:AbstractTable[_]:c.WeakTypeTag](c: Context) = {
import c.universe._
val T = weakTypeOf[T]
q"""new TableQueryBuilder[$T]{
def apply(): TableQuery[$T] = {
TableQuery[$T]
}
}"""
}
}
trait TableQueryBuilder[T<:AbstractTable[_]] {
def apply(): TableQuery[T]
}
object TableQueryBuilder {
implicit def builderForTable[T<:AbstractTable[_]]: TableQueryBuilder[T] = macro TableQueryBuilderMacro.createBuilderImpl[T]
def build[T<:AbstractTable[_]:TableQueryBuilder](): TableQuery[T] = implicitly[TableQueryBuilder[T]].apply()
}
The net effect is that you don't need anymore to know the concrete value of the type T
in order to be able to instantiate a TableQuery[T]
,
provided that you have an implicit instance of TableQueryBuilder[T]
in scope. In other words, you can shift the need to know the concrete value of T
up to the point where you actually know it.
MyBase
(now a class) can then be implemented like this:
class MyBase[M, T <: Table[M] : TableQueryBuilder] {
lazy val query: TableQuery[T] = TableQueryBuilder.build[T]
}
And you can then extend it without the need to explcitly call TableQuery.apply
:
class Coffees(tag: Tag) extends Table[(String, Double)](tag, "COFFEES") {
def name = column[String]("COF_NAME")
def price = column[Double]("PRICE")
def * = (name, price)
}
class Derived extends MyBase[(String, Double), Coffees] // That's it!
What happens here is that in Derived
's constructor, an implicit value for TableQueryBuilder[Coffees]
is implicitly
passed to MyBase
's constructor.
The reason why you cannot apply this pattern if MyBase
were a trait is pretty mundane: trait constructors cannot have parameters, let alone implicit parameters, so there would be no implicit way
to pass the TableQueryBuilder
instance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With