Scala type inference with Slick table

Are there such models (simplified):

case class User(id:Int,name:String) case class Address(id:Int,name:String) ... 

Slick table mapping (2.1.0):

 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] } 

WithId character sign I want to implement common DAO methods for different tables with an id: Column[Int] column id: Column[Int] (I want the findById method findById work with mapping User and Address tables)

 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 does not 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 has type WithId[T, R] and at the same time it has type Table[R] . Slick implements the type Table so that if X=Table[Y] , then X#TableElementType=Y

Therefore, in my case, T#TableElementType=R and Option[T#TableElementType] should be output as Option[R] , but this is not so. Where am I mistaken?

+5
source share
3 answers

Your assumption about WithId[T, R] like Table[R] is wrong. WithId[T, R] -tuning annotations in WithId[T, R] simply require mixing Table[R] , but this does not mean that WithId[T, R] is Table[R] .

I think you confuse the WithId with WithId instances, which should ultimately be an instance of Table .

The restriction of the upper type constraint in the GenericSlickDAO also does not guarantee that the WithId property is an instance of Table , since any type is a subtype itself.

See this question for a more detailed explanation of the differences between types and subtypes.

+1
source

I use play-slick, and I tried to do exactly the same as you, with a tag and using self-type without success.

But I succeeded:

 import modelsunscanned.TableWithId import scala.slick.jdbc.JdbcBackend import scala.slick.lifted.TableQuery import play.api.db.slick.Config.driver.simple._ /** * @author Sebastien Lorber ( lorber.sebastien@gmail.com ) */ 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 example of use:

 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) } 

Please note that active-slick also uses something similar.

+1
source

It helped me highlight. This is a pretty simple genericdao example https://gist.github.com/lshoo/9785645

+1
source

Source: https://habr.com/ru/post/1208773/


All Articles