Question

I am using case classes for models in an ORM. Each model has an id, but the id shouldn't be publicly accessible. So I have a parent trait

trait WithId {
  private var id: Long = 0
}

and a lot of case classes (the models) inheriting from it

case class C1(a: Int, b: String) extends WithId
case class C2(...) extends WithId
...

Now, if someone calls copy() on a case class, it doesn't copy the id with it, but sets it to 0.

val c1 = C1(3, "bla")
//Set c1.id to a value != 0
val c2 = c1.copy(b="bla2")
//c1.id !=0, but c2.id = 0

I want it to copy the id as well.

Since I have a lot of these case classes, I'd prefer to have as few code as possible in the case classes themselves. So implementing a copy() method in each case class would be a lot of boilerplate code.

Is there a way to implement something in the trait that makes copy() also copy the id? Maybe something using macros? Or is there another way I didn't think about at all?

edit:

I could override the id field in each case class like

case class C1(a: Int, b: String, protected var id: Long)

Then it would be copied. But that also is boilerplate code I have to write per case class and it's hard to explain why you have to add an id field to a case class although you never notice it or can use it anywhere else when using the case class. I'd like to avoid that if possible.

Was it helpful?

Solution

If I were you, I'd add an ID-carrying token:

class IdToken private[myPackage] (val id: Int) {
  override def equals(a: Any) = a match {
    case tok: IdToken => id == tok.id
    case _ => false
  }
  override def hashCode = id.hashCode ^ 0x157135
  override def toString = ""
}
object IdToken {
  private var lastId = 0
  private[myPackage] def next = { lastId += 1; new IdToken(lastId) }
}

Since the constructor is private to your package, nobody else but you can create these tokens.

Then you write your trait as

trait WithID { protected def idToken: IdToken }

and

case class C1(a: Int, b: String, protected val idToken: IdToken = IdToken.next) extends WithID {
  def checkId = idToken.id
}

(where you only need checkId for the test below) so you can

scala> C1(5, "fish")
res1: C1 = C1(5,fish,)

scala> res1.copy(a = 3)
res2: C1 = C1(3,fish,)

scala> res1.checkId == res2.checkId
res3: Boolean = true

But you can't access the token from outside code because the val is protected.

Hopefully that is good enough encapsulation.

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