Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Define an abstract class or trait that depends on an implicit

I have this play framework 2 code (simplified):

import formatters.json.IdeaTypeFormatter._

object IdeaTypes extends Controller {

  def list = Action { request =>
    Ok(toJson(IdeaType.find(request.queryString)))
  }

  def show(id: Long) = Action {
    IdeaType.findById(id).map { ideatype =>
      Ok(toJson(ideatype))
    }.getOrElse(JsonNotFound("Type of idea with id %s not found".format(id)))
  }
}

IdeaType class extends Entity, and it's companion object, IdeaType, extends EntityCompanion.

As you may expect, I have this kind of code in every controller, so I'd like to extract the basic behavior to a trait, something like this:

abstract class EntityController[A<:Entity] extends Controller {
  val companion: EntityCompanion
  val name = "entity"

  def list = Action { request =>
    Ok(toJson(companion.find(request.queryString)))
  }
  def show(id: Long) = Action {
    companion.findById(id).map { entity =>
      Ok(toJson(entity))
    }.getOrElse(JsonNotFound("%s with id %s not found".format(name, id)))
  }
}

But I get the following error:

[error] EntityController.scala:25: No Json deserializer found for type List[A]. 
[error] Try to implement an implicit Writes or Format for this type.
[error]     Ok(toJson(companion.find(request.queryString)))
[error]              ^
[error] EntityController.scala:34: No Json deserializer found for type A.
[error] Try to implement an implicit Writes or Format for this type.
[error]       Ok(toJson(entity))
[error]                ^

I don't know how to tell that the implicit Writes will be implemented by the classes implementing the EntityController trait (or inheriting the abstract class EntityController)

-- edit

so far now I'm doing it like this:

abstract class CrudController[A <: Entity](
  val model: EntityCompanion[A],
  val name: String,
  implicit val formatter: Format[A]
) extends Controller {

and use it like this

object CrudIdeaTypes extends CrudController[IdeaType](
  model = IdeaType, 
  name = "type of idea", 
  formatter = JsonIdeaTypeFormatter
)

I could't get scala to automatically pick it using implicits. I tried with this import but it didn't work

import formatters.json.IdeaTypeFormatter._
like image 209
opensas Avatar asked Oct 18 '12 02:10

opensas


1 Answers

If you want the controler classes themselves to define the implicit, then just declare abstract implicit values, and define them in the derived classes.

abstract class EntityController[A<:Entity] extends Controller {
  protected implicit def entityWriter: Writes[A]
  protected implicit def entityListWriter: Writes[List[A]]
  ...      
}

class MyEntity extends Entity {
  ...
}

class MyEntityController extends EntityController[MyEntity] {
  protected def entityWriter: Writes[MyEntity] = ...
  protected def entityListWriter: Writes[List[MyEntity]] = ...    
}

However, it is much more practical to define these implicits outside the controller, typically in the companion object of your entity, so that they the compiler can find them automatically without import. Then, pass the implicit values to the constructor of EntityController:

abstract class EntityController[A<:Entity](implicit entityWriter: Writes[A], entityListWriter: Writes[List[A]] ) extends Controller {
  ...      
}

class MyEntity extends Entity {
  ...
}
object MyEntity {
  protected implicit def entityWriter: Writes[A]
  protected implicit def entityListWriter: Writes[List[A]]
}

class MyEntityController extends EntityController[MyEntity] {
  ...
}

Final note, the implicit to List[MyEntity] is probably unneeded( thus only the implicit for MyEntity would need to be explicitly defined). I have not checked, but usually when using this "typeclass pattern", the framework will already define an implicit for each List[T], provided that there is an implicit for T. This is probably the case, though I have not checked.

like image 185
Régis Jean-Gilles Avatar answered Sep 18 '22 14:09

Régis Jean-Gilles