1

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?

Bruno Batarelo
  • 315
  • 2
  • 11
  • possible duplicate of [Scala reflection to instantiate scala.slick.lifted.TableQuery](http://stackoverflow.com/questions/30183000/scala-reflection-to-instantiate-scala-slick-lifted-tablequery) – Sean Vieira Sep 11 '15 at 14:57
  • 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 Sep 11 '15 at 22:59

2 Answers2

2

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.

S.Karthik
  • 1,389
  • 9
  • 21
  • Although Idea doesn't complain, it doesn't build. Maybe the best thing is to paste the compile error: type arguments [models.dao.Accounts] do not conform to class GenericRepository's type parameter bounds [T <: slick.lifted.AbstractTable[T]] – Bruno Batarelo Sep 11 '15 at 18:47
  • @BrunoBatarelo, I updated the answer, you just have to change the Type of AbstractTable to _ . that will work fine, without any error. – S.Karthik Sep 14 '15 at 06:13
1

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.

Allen Chou
  • 1,229
  • 1
  • 9
  • 12