Question

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

Was it helpful?

Solution

It's not really a Squeryl thing. As I understand it, the problem is that the pattern match testing is done at runtime, after type erasure has occurred. Scala is able to keep the type information around with the TypeTag and perform the runtime check but it can't infer that the types are correct at compile time. If you were to try something like

case t: ClassTag[User] => users

Which is asking the compiler to do a static check, you would get a warning that the User type is erased. The way you are doing it should work since it should be fine to perform the cast after you've verified the type, and I don't think there is a better way.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top