Question

I've joined the proud ranks of people trying to use Squeryl as my ORM framework for a web app. (For the record, I'm using Scalatra as the actual web framework -- but I don't think this is a Scalatra question.) This means I've joined the ranks of people trying to build an effective abstraction layer to DRY our common operations. For instance, it's pretty common to see examples like so:

// First Model

package com.myproj
import com.myproj.Schema

class Foo() extends KeyedEntity {
  val id = 0
  def getAll() = { from(Schema.Foo)(s => select(s)) }    
}

// Different Model

package com.myproj
import com.myproj.Schema

class Bar() extends KeyedEntity {
  val id = 0    
  def getAll() = { from(Schema.Boo)(s => select(s)) }   
}

So on the one hand, I'm digging how terse Squeryl's syntax is. On the other: this is pretty repetitive. What I want is something more like:

// Base

class BaseEntity extends KeyedEntity {
  val id = 0
  def getAll() = { from(table)(s => select(s)) }
}

// New model

class Foo extends BaseEntity

// New model

class Bar extends BaseEntity

So I mostly have this working. Extending KeyedEntity is pretty straight forward. There's only one problem: how the heck do you define a table in BaseEntity such that classes which extend it can access it? To be honest, this might fundamentally be a question of my not having a sophisticated enough understanding of the Scala type system. I'm posing it here anyways.

I've tried a couple things:

  1. Declaring val table in an abstract BaseEntity. This has gotten me in to a pretty ridiculous type checking mess. val table: Table[T] only works if I also define T as a type, and then the child classes cause compiler errors when they try to provide a table of a different type.
  2. Write the base class to expect that tables be passed in as an argument to each function. This means every model still has to call it's parent method to pass the table argument.
  3. I've hacked around with this SO post, which uses TypeTags. However, the poster didn't provide quite enough information for me to understand his implementation.
  4. The aforementioned SO post has a comment in which the org.squeryl.Schema.findTablesFor method is suggested. Again with the potential newbie issues: I haven't made headway with how to implement that as an answer. I've tried things like:

    class BaseEntity {
    val table = findTablesFor(this)
    }

But then, I get back an Iterable, and I'm a little unsure what to do with it.

So. Is there a "right" way to do this? Surely there's a clean way to move CRUD operations into a base class -- I just can't seem to figure it out.

Edit

So, here's what I've got, using Squeryl 9.5-6:

// Schema
package com.myproj.schema
object MySchema extends Schema {
  val foo = table[Foo]("foos")
  val bar = table[Bar]("bars")
}

// BaseEntity
package com.myproj.models
import com.myproj.schema.MySchema
abstract class BaseEntity extends Keyedentity[Long] {
  val id: Long = 0
  val table = MySchema.findTablesFor(this).head
}

// Class
package com.myproj.models
case class Foo (
  val name: String,
  val extra: Option[String]
) extends BaseEntity {
  def this() = this("", None)
}

Setup like this. findTablesFor always returns an empty iterator. It compiles, but throws errors at runtime for trying to call head on an empty iterator (as you said it would). Handling the error isn't a problem; not being able to find a table kinda is.

Thoughts?

Was it helpful?

Solution

val table: Table[T] only works if I also define T as a type, and then the child classes cause compiler errors when they try to provide a table of a different type.

You can do this with a self type. I'm not sure I'd recommend it, but it should work:

class BaseEntity[T] {
  self: T =>

  val table: Table[T]

}

Then your implementation would look like:

class MyEntity extends BaseEntity[MyEntity]

Using findTablesFor is probably a better solution. There is nothing stopping you from mapping a Class to multiple tables within a Squeryl schema. You could have:

val tableA = table[MyEntity]

val tableB = table[MyEntity]

Hence the reason to return an Iterable for all of the matching tables. If you know you aren't going to do this though, you can just use the first result:

val table = MySchema.findTableFor(this).head

Note that this will throw an exception if no relevant tables are found.

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