2014-12-11 11 views
5

hanno tali modelli (semplificato):Scala inferenza di tipo, lavorando con tavolo Slick

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

Slick (2.1.0 versione) tabella di 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] 
} 

miscelazione tratto WithId Voglio realizzare DAO generico metodi per diverse tabelle con colonna id: Column[Int] (Voglio metodo findById per lavorare con entrambe le associazioni di tabelle User e Address)

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

Il problema è che findById non compila:

Error:(13, 45) type mismatch; found : Option[T#TableElementType] required: Option[R] stk.tableQuery.filter(_.id === id).list.headOption

come la vedo io T è di tipo WithId[T, R] e al tempo stesso è di tipo Table[R]. Slick implementa il tipo Table tale che se X=Table[Y] quindi X#TableElementType=Y.

Quindi nel mio caso T#TableElementType=R e Option[T#TableElementType] devono essere dedotti come Option[R] ma non lo è. Dove mi sbaglio?

risposta

1

L'assunzione di WithId[T, R] di tipo Table[R] è errata. L'annotazione self-type in WithId[T, R] richiede semplicemente un Table[R] da mescolare, ma ciò non significa che WithId[T, R] è un Table[R].

Penso che si confonda la dichiarazione di WithId con istanze di WithId che alla fine deve essere un'istanza di Table.

Il vincolo del limite superiore del carattere nel tratto GenericSlickDAO non garantisce inoltre che la proprietà di WithId sia un'istanza di Table, poiché qualsiasi tipo è un sottotipo di se stesso.

Vedere la domanda this per una spiegazione più dettagliata sulle differenze tra tipi di auto e sottotipi.

1

Sto usando il gioco di parole e ho cercato di fare esattamente come te, con un tratto e utilizzando self-type senza successo.

Ma sono riuscito con il seguente:

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

Ti faccio un utilizzo exemple:

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

noti che active-slick è anche utilizzando qualcosa di simile