8
votes

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._
1
+1, have thought the same, how to abstract away common CRUD operations in Play. Your case, JSON response, is easier than dealing with mixed JSON and views.html content + authentication. Lot's o' boilerplate...virtualeyes

1 Answers

7
votes

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.