Reputation: 83
I'm trying to model a trait/class structure to a generic Data Access Layer using Slick, but to limit just on fundamental part I'll post a basic code that have the general idea of the problem.
Suppose I have a base trait that define the common attributes for a Entity.
trait Base {
def id: Option[Long]
def version: Long
}
Now I'll create the Entities based on that trait.
case class User(id: Option[Long], version: Long, name: String, age: Int) extends Base
And a function that will get the Base type to be generic. At that point I can create, update the objects on database without problem. My problem is when I want to return the original object with the generated id returned from database.
def insert[M <: Base](m: M): M = {
//insert on database and get the new ID
val newId = Some(10)
// Now I want to return the object with the newId
// As Base is a trait I don't have copy
m
}
val user = User(None, 1, "Test", 34)
insert(user)
To illustrate I would like to get a new User with id = Some(10) as result for insert function.
I thought about use copy, but it will work if I declare the function with a Base case class instead a Trait, but isn't what I want initially. I tried to use Lens, like scalaz Lens. But I'll need copy too.
I'm missing something? There is another way to do it without using Reflection?
Thanks
Upvotes: 2
Views: 244
Reputation: 16324
You could use F-bound polymorphism to require a withId
method on the trait that is required to return the expected type:
trait Base[Self <: Base[Self]] { self: Self =>
def id: Option[Long]
def version: Long
def withId(id: Long): Self
}
You can then implement withId
on any case class by calling its native copy
method:
case class User(id: Option[Long], version: Long, name: String, age: Int) extends Base[User] {
def withId(id: Long) = this.copy(id = Some(id))
}
Then you could define your insert
method as:
def insert[M <: Base[M]](m: M): M = {
m.withId(10)
}
And the rest should work as expected.
The real problem is that copy
is a special compiler-generated method, and you cannot require its existence on a trait. This use of F-bound polymorphism allows you to work around this limitation with limited boilerplate.
Another approach is to just add another trait, say HasWithId
that guarantees the withId
method, and extend/require that wherever needed:
trait Base {
def id: Option[Long]
def version: Long
}
trait HasWithId[M] {
def withId(id: Long): M
}
case class User(id: Option[Long], version: Long, name: String, age: Int) extends Base with HasWithId[User] {
def withId(id: Long): User = this.copy(id = Some(id))
}
def insert[M <: Base with HasWithId[M]](m: M) = {
m.withId(10)
}
Upvotes: 3