1
votes

I am experiencing issues making Slick's TableQuery used in a generic fashion.

Observe the regular situation:

class AccountRepository {
override protected val dbConfig = DatabaseConfigProvider.get[JdbcProfile](Play.current)
val accounts = TableQuery[Accounts]
def all = db.run(accounts.result)
...

The idea would be to extract everything possible into generic trait or abstract class in order to avoid repetition. For the sake of simplicity I included only the problematic code.

abstract class GenericRepository[T] extends HasDatabaseConfig[JdbcProfile] {
override protected val dbConfig = DatabaseConfigProvider.get[JdbcProfile(Play.current)
val table = TableQuery[T]
}

And to use it like:

class AccountRepository extends GenericRepository[Accounts] {

However, that creates a compilation error:

type arguments [T] conform to the bounds of none of the overloaded alternatives of value apply: [E <: slick.lifted.AbstractTable[]]=> slick.lifted.TableQuery[E] [E <: slick.lifted.AbstractTable[]](cons: slick.lifted.Tag => E)slick.lifted.TableQuery[E]

Trying to fix the issue by setting a boundary doesn't help as well.

abstract class GenericRepository[T <: slick.lifted.AbstractTable[T]] extends HasDatabaseConfig[JdbcProfile] {

However, we end up with a different error:

class type required but T found

at following place:

val table = TableQuery[T]

Any idea about the solution?

2
Fundamentally questions are similar in a sense that they address the same issue, but the approach is somewhat different. Also, correct answer below is more applicable to this concrete situation and is overall quite valuable.Bruno Batarelo

2 Answers

1
votes

I guess if you can solve the initialization of tableQuery, then you can continue your GenericRepository. I am using Slick 3.0 with PostgreSQL.

In the slick.lifted.TableQuery, there is a method like the following

// object TableQuery
def apply[E <: AbstractTable[_]](cons: Tag => E): TableQuery[E] =
    new TableQuery[E](cons)

So if we can get an instance of E on the fly, then we can get a generic way to create TableQuery. So reflection seems to be a possible way to solve it.

 import scala.reflect.runtime.{ universe => ru }
 import slick.lifted.{ AbstractTable, ProvenShape, Tag }
 import slick.driver.PostgresDriver.api._


  object Reflection {
    val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)

    def getTypeTag[T: ru.TypeTag] = ru.typeTag[T]

    def createClassByConstructor[T: ru.TypeTag](args: Any*) =
      runtimeMirror.reflectClass(getTypeTag[T].tpe.typeSymbol.asClass)  
       .reflectConstructor(ru.typeOf[T].declaration(ru.nme.CONSTRUCTOR)
       .asMethod)(args: _*).asInstanceOf[T]
  }


  // context bound here is for createClassByConstructor to use
  abstract class GenericTableQuery[U, T <: AbstractTable[U]: ru.TypeTag] {

    import Reflection._

    // look at following code: Students, if you want to initialize Students
    // you're gonna need a tag parameter, that's why we pass tag here
    val tableQuery = TableQuery.apply(tag => createClassByConstructor[T](tag))

  }

 // Sample Table
 case class Student(name: String, age: Int)
 class Students(tag: Tag) extends Table[Student](tag, "students") {
    def name = column[String]("name")
    def age = column[Int]("age")
    override def * : ProvenShape[Student] = (name, age) 
      <> (Student.tupled, Student.unapply _)
 }

 // get TableQuery
 object TestGenericTableQuery extends GenericTableQuery[Student, Students] {
    val studentQuery = tableQuery
 }

The codes mentioned above is just focused on the issue of generic TableQuery, try to combine it with your GenericRepository and your problem may get solved.

Anyway, hope it helps.

2
votes

You have to pass table query manually,

 abstract class GenericRepository[T <: slick.lifted.AbstractTable[_]](query: TableQuery[T])

and in implementation,

class AccountRepository extends GenericRepository[Accounts](TableQuery[Accounts])

I hope this will solve your problem.