Inheritance with companion Objects
Asked Answered
R

1

5

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.

Rabon answered 18/4, 2016 at 2:15 Comment(0)
T
7

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
}
Thousandth answered 18/4, 2016 at 3:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.