3
votes

I am working on a play 2.1 project and need some guidance on a scala design problem. For our application, a request context object which stores client information from the incoming request is needed at the model layer.

case class ClientContext(clientName: String) 

object ClientContext {
  def apply(request: Request) = {
    new ClientContext(request.params("clientName")) //pseudo code
  }
}

my model

object MyDAO {
  def findAll(context: ClientContext) = { ... }
}

Then in our controller, we need to pass it into model's dao method:

object MyController extends Controller {
  def index = Action { implicit request => 
    val results = MyDAO.findAll(ClientContext(request))
    Ok(results)
  }
}

The implicit request is provided by the Action class (I suppose) The problem with this approach is that I need to write implicit request => and ClientContext(request) for every controller action that calls MyDAO.findAll.

Is there a way to improve the code through an Action wrapper and implicit value? I would like to be able to declare context: ClientContext as an implicit parameter in the MyDAO.findAll method and write my actions in the following way:

object MyDAO {
  def findAll(implicit context: ClientContext) = { ... }
}

def index = ActionWithContext {
  val results = MyDAO.findAll
  Ok(results)
} 

Is it possible to write an ActionWithContext (method or object with an apply method) to achieve that? The closest I have now is the following

def ActionWithContext(action: ClientContext => Result) = {
  Action { implicit request =>
    action(ClientContext(request))
  }
} 

usage

def index = ActionWithContext { context => 
  val results = MyDAO.findAll(context)
  Ok(results)
}

Any suggestion to improve this design will be helpful. Thanks!

PS: Honestly I wouldn't even think about further simplifying the code if this is on Java, but since it's scala, I figured it might be good chance to learn some scala patterns.

2

2 Answers

3
votes

I've implemented something similar using implicits:

I call mine Header instead of Context but we're both accomplishing the same thing.

All of my controllers mixin the Header trait:

object Accounts extends AuthController with Header { ... }

My Header trait looks something like this:

trait Header {
    implicit def withUserInfo(implicit maybeUser: Option[User]): UserInfo = {
        // create user info object
    }
}

Then I can write my controller actions like so:

def index = MaybeAuthenticated { implicit maybeUser => implicit request =>
    // do stuff
    val foo = new Foo()
    Ok(views.html.accounts.index(foo))
}

Where the template has a method signature like so:

@(foo: Foo)(implicit userInfo: UserInfo)

MaybeAuthenticated is just an action which optionally restores a User object, it's from the play20-auth module. In fact, I've shown you two possibilities here:

  1. Mixin a trait with an implicit function which takes implicit parameters.
  2. Write your own action method like MaybeAuthenticated

MaybeAuthenticated looks like this:

private def maybeAuthenticated(f: Option[Account] => Request[AnyContent] => Result): Action[AnyContent] = {
    Action(BodyParsers.parse.anyContent)(req => f(restoreUser(req))(req))
}

protected def MaybeAuthenticated = maybeAuthenticated _

I would argue that the first method is easier to understand.

edit: I think some further explanation of implicits is warranted.

Let's consider where implicit is used above:

implicit def withUserInfo(implicit maybeUser: Option[User]): UserInfo

In an object that mixes in Header, this method will be in scope. The compiler will search for functions which require a UserInfo object to be in scope where a Option[User] is already in scope. The compiler will implicitly call withUserInfo to provide the missing UserInfo object.

Note the implicit UserInfo object required by my template. When I call this template function (the Ok(...) invocation), the compiler has to fill in the implicit UserInfo object. It will do so by calling withUserInfo and passing the in-scope implicit maybeUser.

Hope that clarifies implicits a little.

1
votes

Thanks to Ryan's suggestion here is the another solution In ClientContext object make the apply method also an implicit conversion that takes in an implicit parameter

object ClientContext {
  implicit def apply(implicit request: Request) = {
    new ClientContext(request.params("clientName")) //pseudo code
  }
}

Then in the controller you can write

def index = Action { implicit request => 
  val results = MyDAO.findAll
  Ok(results)
}

I am not sure if there is way to get rid of the implicit request but this is pretty terse to me now.