Have such models (simplified):
case class User(id:Int,name:String)
case class Address(id:Int,name:String)
...
Slick (2.1.0 version) table mapping:
class Users(_tableTag: Tag) extends Table[User](_tableTag, "users") with WithId[Users, User] {`
  val id: Column[Int] = column[Int]("id", O.AutoInc, O.PrimaryKey)
  ...
}
trait WithId[T, R] {
  this: Table[R] =>
  def id: Column[Int]
}
Mixing trait WithId I want to implement generic DAO methods for different tables with column id: Column[Int]  (I want method findById to work with both User and Address table mappings)
trait GenericSlickDAO[T <: WithId[T, R], R] {
  def db: Database
  def findById(id: Int)(implicit stk: SlickTableQuery[T]): Option[R] = db.withSession { implicit session =>
    stk.tableQuery.filter(_.id === id).list.headOption
  }
trait SlickTableQuery[T] {
  def tableQuery: TableQuery[T]
}
object SlickTableQuery {
  implicit val usersQ = new SlickTableQuery[Users] {
    val tableQuery: Table Query[Users] = Users
  }
}
The problem is that findById doesn't compile:
Error:(13, 45) type mismatch; found : Option[T#TableElementType] required: Option[R] stk.tableQuery.filter(_.id === id).list.headOption
As I see it T is of type WithId[T, R] and at the same time is of type Table[R]. Slick implements the Table type such that if X=Table[Y] then X#TableElementType=Y.
So in my case T#TableElementType=R and Option[T#TableElementType] should be inferred as Option[R] but it isn't. Where am I wrong?
Your assumption about WithId[T, R] being of type Table[R] is wrong. The self-type annotation in WithId[T, R] just requires a Table[R] to be mixed in, but that doesn't mean that WithId[T, R] is a Table[R].
I think you confuse the declaration of WithId with instances of WithId which eventually need to be an instance of a Table.
Your upper type bound constraint in the GenericSlickDAO trait also doesn't guarantee you the property of WithId to be an instance of Table, since any type is a subtype of itself.
See this question for a more elaborate explanation about the differences between self-types and subtypes.
I'm using play-slick and I tried to do exactly like you, with a trait and using self-type without success.
But I succeeded with the following:
import modelsunscanned.TableWithId
import scala.slick.jdbc.JdbcBackend
import scala.slick.lifted.TableQuery
import play.api.db.slick.Config.driver.simple._
/**
 * @author Sebastien Lorber ([email protected])
 */
package object models {
  private[models] val Users = TableQuery(new UserTable(_))
  private[models] val Profiles = TableQuery(new ProfileTable(_))
  private[models] val Companies = TableQuery(new CompanyTable(_))
  private[models] val Contacts = TableQuery(new ContactTable(_))
  trait ModelWithId {
    val id: String
  }
  trait BaseRepository[T <: ModelWithId] {
    def tableQuery: TableQuery[TableWithId[T]]
    private val FindByIdQuery = Compiled { id: Column[String] =>
      tableQuery.filter(_.id === id)
    }
    def insert(t: T)(implicit session: JdbcBackend#Session) = {
      tableQuery.insert(t)
    }
    def getById(id: String)(implicit session: JdbcBackend#Session): T = FindByIdQuery(id).run.headOption
      .getOrElse(throw new RuntimeException(s"Could not find entity with id=$id"))
    def findById(id: String)(implicit session: JdbcBackend#Session): Option[T] = FindByIdQuery(id).run.headOption
    def update(t: T)(implicit session: JdbcBackend#Session): Unit = {
      val nbUpdated = tableQuery.filter(_.id === t.id).update(t)
      require(nbUpdated == 1,s"Exactly one should have been updated, not $nbUpdated")
    }
    def delete(t: T)(implicit session: JdbcBackend#Session) = {
      val nbDeleted = tableQuery.filter(_.id === t.id).delete
      require(nbDeleted == 1,s"Exactly one should have been deleted, not $nbDeleted")
    }
    def getAll(implicit session: JdbcBackend#Session): List[T] = tableQuery.list
  }
}
// play-slick bug, see https://github.com/playframework/play-slick/issues/227
package modelsunscanned {
   abstract class TableWithId[T](tableTag: Tag,tableName: String) extends Table[T](tableTag,tableName) {
    def id: Column[String]
  }
}
I give you an exemple usage:
object CompanyRepository extends BaseRepository[Company] {
  // Don't know yet how to avoid that cast :(
  def tableQuery = Companies.asInstanceOf[TableQuery[TableWithId[Company]]] 
  // Other methods here
  ...
}
case class Company(
                    id: String = java.util.UUID.randomUUID().toString,
                    name: String,
                    mainContactId: String,
                    logoUrl: Option[String],
                    activityDescription: Option[String],
                    context: Option[String],
                    employeesCount: Option[Int]
                    ) extends ModelWithId
class CompanyTable(tag: Tag) extends TableWithId[Company](tag,"COMPANY") {
  override def id = column[String]("id", O.PrimaryKey)
  def name = column[String]("name", O.NotNull)
  def mainContactId = column[String]("main_contact_id", O.NotNull)
  def logoUrl = column[Option[String]]("logo_url", O.Nullable)
  def activityDescription = column[Option[String]]("description", O.Nullable)
  def context = column[Option[String]]("context", O.Nullable)
  def employeesCount = column[Option[Int]]("employees_count", O.Nullable)
  //
  def * = (id, name, mainContactId,logoUrl, activityDescription, context, employeesCount) <> (Company.tupled,Company.unapply)
  //
  def name_index = index("idx_name", name, unique = true)
}
Note that active-slick is also using something similar
This helped me out a lot. It's a pretty simple example of a genericdao https://gist.github.com/lshoo/9785645
package slicks.docs.dao
import scala.slick.driver.PostgresDriver.simple._
import scala.slick.driver._
trait Profile {
  val profile: JdbcProfile
}
trait CrudComponent {
  this: Profile =>
  abstract class Crud[T <: Table[E] with IdentifiableTable[PK], E <: Entity[PK], PK: BaseColumnType](implicit session: Session) {
    val query: TableQuery[T]
    def count: Int = {
      query.length.run
    }
    def findAll: List[E] = {
      query.list()
    }
    def queryById(id: PK) = query.filter(_.id === id)
    def findOne(id: PK): Option[E] = queryById(id).firstOption
    def add(m: E): PK = (query returning query.map(_.id)) += m 
    def withId(model: E, id: PK): E 
    def extractId(m: E): Option[PK] = m.id
    def save(m: E): E = extractId(m) match {
      case Some(id) => {
        queryById(id).update(m)
        m
      }
      case None => withId(m, add(m))
    }
    def saveAll(ms: E*): Option[Int] = query ++= ms
    def deleteById(id: PK): Int = queryById(id).delete
    def delete(m: E): Int = extractId(m) match {
      case Some(id) => deleteById(id)
      case None => 0
    }
  }
}
trait Entity[PK] {
  def id: Option[PK]
}
trait IdentifiableTable[I] {
  def id: Column[I]
}
package slicks.docs
import slicks.docs.dao.{Entity, IdentifiableTable, CrudComponent, Profile}
case class User(id: Option[Long], first: String, last: String) extends Entity[Long]
trait UserComponent extends CrudComponent {
  this: Profile =>
  import profile.simple._
  class UsersTable(tag: Tag) extends Table[User](tag, "users") with IdentifiableTable[Long] {
    override def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
    def first = column[String]("first")
    def last = column[String]("last")
    def * = (id.?, first, last) <> (User.tupled, User.unapply)
  }
  class UserRepository(implicit session: Session) extends Crud[UsersTable, User, Long] {
    override def query = TableQuery[UsersTable]
    override def withId(user: User, id: Long): User = user.copy(id = Option(id))
  }
}
                        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