I'm working on my first substantial project using Scala, Scalatra, and Squeryl, and happened upon the following problem:
I wanted to have an abstract base class for my DAOs that provided a simple implementation of the basic CRUD operations (create, read, update, delete), so I needed a way for said abstract base class to be aware of which table to reference.
With Squeryl, you map your data classes to actual tables in a singleton object that extends squeryl.Schema, and your DAOs are generally companion objects for each class.
I came up with the following solution using type tags:
First, an excerpt of the base class from which all DAOs will inherit (Note: DBRecord is a sub of Squeryl's KeyedEntity):
abstract class CrudOps[S <: DBRecord](implicit tt: TypeTag[S]) {
def create(item: S)= {
inTransaction{
val result = ATSchema.recordTable.insert(item)
}
}
Next, the recordTable function in ATSchema:
object ATSchema extends Schema {
val users = table[User]
def recordTable[T <: DBRecord](implicit tt: TypeTag[T]): Table[T] = tt.tpe match {
case t if t =:= typeOf[User] => users.asInstanceOf[Table[T]]
//...other table types go here
case _ => throw new IllegalArgumentException("Unknown DBRecord type")
}
}
Now, this works, I have several tables and CrudOps grabs the right one and does its stuff. But there's something I'm not understanding (I'm still fairly new to Scala): Why do I need to cast my table vals in recordTable() to Table[T]? If I remove the .asInstanceOf, I get a type mismatch, but users is of the type Table[User]... seems like it ought to be unnecessary. Also, this has the feel of a complicated solution to what should be a trivial problem (maybe I'm abusing the type system), and also couples CrudOps to the Schema (which I would like to avoid), so I am certainly open to suggestions from folks with more Scala and/or Squeryl experience than I :)