Question

I would like to factor some common attributes of my case classes, say an id and a timestamp, to write generic code these. The following works fine, but I have to repeat all attributes on each case class:

trait HasIdAndTimestamp {
  val id: Int
  val timestamp: Long
}
case class Client(id: Int, timestamp: Long, name: String) extends HasIdAndTimestamp
case class Order(id: Int, timestamp: Long, items: List[Int], clientId: Int) extends HasIdAndTimestamp
...

Another option would be to have these common fields on a single field of type MetaData:

case class MetaData(id: Int, timestamp: Long)
trait HasMetaData {
  val metadata: MetaData
}
case class Client(metadata: MetaData, name: String) extends HasMetaData
case class Order(metadata: MetaData, items: List[Int], clientId: Int) extends HasMetaData
...

But I don't really like to write .metadata.id all the time. Also I want to store these cases classes in a DB using Slick, and this nesting introduces a lot of additional boilerplate in the table definition. I would rather like something similar to the following:

case class MetaData(id: Int, timestamp: Long)
case class Client(name: String) extends MetaData
case class Order(items: List[Int], clientId: Int) extends MetaData
...

Where Client and Order both have these id and timestamp (val) field and apply/unapply defined accordingly.

Could shapeless/scalaz help me here?

Was it helpful?

Solution

How about this?

// type def for Metadata
object MetadataType{ // singleton wrapper, because type defs can't be toplevel
  type MetaData = (Int,Long)
}
import MetadataType.Metadata

// unpack using pattern matching
class HasMetaData(metadata: Metadata) {
  val (id,timestamp) = metadata
}
case class Client(metadata: MetaData, name: String) extends HasMetaData(metadata)
case class Order(metadata: MetaData, items: List[Int], clientId: Int) extends HasMetaData(metadata)

// alternative: even avoid constructor argument
trait HasMetaData {
  def metadata: Metadata
  lazy val (id,timestamp) = metadata // lazy to avoid init order probs
}
case class Client(metadata: MetaData, name: String) extends HasMetaData
case class Order(metadata: MetaData, items: List[Int], clientId: Int) extends HasMetaData

Then in Slick:

...
def * = ((id,timestamp), name) <> (Client.tupled, Client.unapply)
...

Not sure items: List[Int] is for an Order. If it is a foreign key of a separate association table it shouldn't be here. A Order Table class is meant for describing exactly one database table. If you want to assemble data from several tables, write it externally as a value of type (Order,List[Int]) instead. You can implement it using a join with a subsequent groupBy on the client side or two separate queries. At some point we would like to support such a groupBy in Slick, which actually returns a nested collection, but at the moment you have to write it on the client side.

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