2015-05-12 13 views
8

ho questa caratteristica di baseScala di riflessione per istanziare scala.slick.lifted.TableQuery

trait MyBase { 
    type M 
    type T <: Table[M] 
    val query: TableQuery[T] 
} 

Dove TableQuery è scala.slick.lifted.TableQuery

miei sottoclassi istanziare TableQuery in questo modo:

type M = Account 
type T = AccountsTable 
val query = TableQuery[T] 

I' Mi piacerebbe istanziare il TableQuery nel tratto base, possibilmente usando un lazy val, ovvero

lazy val query: TableQuery[T] = { 
    ... 
} 

Ho giocato con la riflessione, ma non ho avuto molta fortuna.

+0

Forse questa è una domanda stupida, ma qual è lo scopo di istanziare il TableQuery nel tratto (è difficile capire il valore, perché, sarà diverso per ogni tipo che funziona con il tratto) - forse aggiungendo questo potrebbe aggiungere più contesto per qualcuno per fornire una risposta migliore. –

risposta

8

Se ho capito bene, ciò che si desidera è di essere in grado di estendere MyBase semplicemente definendo M e T, ma senza dover istanziare esplicitamente il TableQuery in ogni classe derivata.

utilizzando la riflessione non è davvero un'opzione perché normalmente si utilizza TableQuery.apply per questo (come in val query = TableQuery[MyTable]), e questo è attuato mediante una macro, quindi hai un "runtime vs tempo di compilazione" questione.

Se è assolutamente necessario che MyBase sia un tratto (al contrario di una classe), quindi non vedo alcuna soluzione praticabile. Tuttavia, se è possibile convertire e in virgola mobile M e T in parametri di tipo (anziché in tipi astratti), tuttavia esiste almeno una soluzione. Come ho accennato in un'altra domanda correlata (How to define generic type in Scala?), è possibile definire una classe di tipo (ad esempio TableQueryBuilder) per acquisire la chiamata a TableQuery.apply (nel punto in cui è noto il tipo concreto) insieme a una macro implicita (ad esempio) a fornire un'istanza di questa classe di tipi. È quindi possibile definire un metodo (ad esempio TableQueryBuilder.build) per creare un'istanza dello TableQuery, che delegherà semplicemente il lavoro alla classe del tipo.

// NOTE: tested with scala 2.11.0 & slick 3.0.0 
import scala.reflect.macros.Context 
import scala.language.experimental.macros 
object TableQueryBuilderMacro { 
    def createBuilderImpl[T<:AbstractTable[_]:c.WeakTypeTag](c: Context) = { 
    import c.universe._ 
    val T = weakTypeOf[T] 
    q"""new TableQueryBuilder[$T]{ 
     def apply(): TableQuery[$T] = { 
     TableQuery[$T] 
     } 
    }""" 
    } 
} 
trait TableQueryBuilder[T<:AbstractTable[_]] { 
    def apply(): TableQuery[T] 
} 
object TableQueryBuilder { 
    implicit def builderForTable[T<:AbstractTable[_]]: TableQueryBuilder[T] = macro TableQueryBuilderMacro.createBuilderImpl[T] 
    def build[T<:AbstractTable[_]:TableQueryBuilder](): TableQuery[T] = implicitly[TableQueryBuilder[T]].apply() 
} 

L'effetto netto è che non è necessario più conoscere il valore concreto del tipo T al fine di essere in grado di creare un'istanza di un TableQuery[T], purché si disponga un'istanza implicita di TableQueryBuilder[T] portata. In altre parole, è possibile spostare la necessità di conoscere il valore concreto di fino al punto in cui lo si conosce effettivamente.

MyBase (ora una classe) che possono essere attuati in questo modo:

class MyBase[M, T <: Table[M] : TableQueryBuilder] { 
    lazy val query: TableQuery[T] = TableQueryBuilder.build[T] 
} 

e si può quindi estendere senza la necessità di explcitly chiamare TableQuery.apply:

class Coffees(tag: Tag) extends Table[(String, Double)](tag, "COFFEES") { 
    def name = column[String]("COF_NAME") 
    def price = column[Double]("PRICE") 
    def * = (name, price) 
} 

class Derived extends MyBase[(String, Double), Coffees] // That's it! 

Cosa succede qui è che nel costruttore Derived, un valore implicito per TableQueryBuilder[Coffees] è implicitamente passato al costruttore MyBase.

Il motivo per cui non è possibile applicare questo schema se MyBase erano un tratto è abbastanza banale: costruttori tratto non possono avere parametri, per non parlare di parametri impliciti, quindi non ci sarebbe modo implicito di passare l'istanza TableQueryBuilder.

+0

Grazie per aver trovato il tempo di mettere insieme questa risposta. Un sacco di grandi informazioni qui. – ic3b3rg

+0

Anche se questa è un'ottima soluzione, tuttavia sto ancora cercando di capire come astrarre un semplice 'db.run (query.result)' come risultato non può essere risolto. Esiste comunque qualche modo per implementare una sorta di Data Mapper con Slick? Grazie – ramsvidor

+0

Non sono sicuro di aver capito il tuo problema. Potresti voler pubblicare una domanda appropriata con lo snippet di codice pertinente che mostri il problema. –

Problemi correlati