I am trying to refactor some models I have that currently look like this:
case class Person(name: String, age: Int)
object Person {
implicit val reads: Reads[Person] = (
(JsPath \ "name").read[String] and
(JsPath \ "age").read[Int]
)(Person.apply _)
}
To something that looks like this:
abstract class BaseModel {
val pk: String // Some stuff here that will be common
}
object BaseModel {
implicit val reads: Reads[BaseModel] // Not sure what to do here
}
So that I can do this:
trait MyTrait[Model <: BaseModel] {
// More code here
val body: JsObject = ...
val parsed = body.validate[Model] // Error: There is no implicit value defined for Model
}
case class NewPerson extends BaseModel {...}
object NewPerson {...} // Maybe need to extend something here
class MyController extends MyTrait[NewPerson]
I want every model to define an implicit reads value, but I am unsure about how to indicate this in the abstract class's companion object.
There isn't a language feature that will force an abstract class/companion pair to be extended together. I've overcome this missing link by making the "companion" of the abstract class a trait. Something like this:
abstract class BaseModel {
val pk: String
}
trait ModelCompanion[A <: BaseModel] { self: Singleton =>
implicit val reads: Reads[A]
}
case class Person(name: String, age: Int) extends BaseModel
object Person extends BaseModel[Person] {
...
}
Unfortunately, this still doesn't tell MyTrait
(as defined in the OP) that a Model <: BaseModel
has a companion where an implicit Reads[Model]
can be found. Again, the link needs to be made manually by requiring MyTrait
to hold a reference to the companion object of the model.
trait MyTrait[Model <: BaseModel] {
def companion: ModelCompanion[Model]
implicit def reads: Reads[Model] = companion.reads
// More code here
val body: JsObject = ...
val parsed = body.validate[Model] // Now this would work
}
object MyTraitImpl extends MyTrait[Person] {
def companion = Person
}