How to simplify your action’s validation code in Play Framework 2 (with Scala of course)

In web development you very often want to do some checks if the data passed through parameters are valid. I’m not talking about having valid format but if for example the corresponding entry exists or if the user has the right to access that information or not. I’ll be a bit more specific by using an example and you’ll get my point. Ok, let’s consider that we want to make a web app which manages devices and users. A device has a unique id and a name. A user has a unique id, a name and a set of devices attached to him. Let’s assume we wrote all the necessary code and we want to provide an action that attaches a device to a user. How would we do that? My first approach would be something like this:

object Users extends Controllers {

  def attachDevice(user_id : Long, device_id : Long) = Action {
    models.User.getById(user_id).map { user_id =>
      models.Device.getById(device_id).map { device_id =>
        if (models.User.attach(user_id, device_id))
          Ok("succeeded")
        else
          InternalServerError
      }.getOrElse { BadRequest("Invalid device id") }
    }.getOrElse { BadRequest("Invalid user id") }
  }

}

That’s fine! It’s not a bad approach. We do all the necessary tests. Code is more or less readable. It’s clear what we return in each case. But what happens when we want to have another action that needs to check if the user_id or device_id is valid? Would we rewrite them?

Not really! We would refactor them!

Great! Let’s create a trait Security and put our validations there.

trait Security {
  import play.api.mvc.Results._

  def withValidDevice(device_id : Long)(result : Result) = {
    if (models.Device.getById(device_id).nonEmpty)
      result
    else
      BadRequest( "Invalid device id" )
  }

  def withValidUser(user_id : Long)(result : Result) = {
    if (models.User.getById(user_id).nonEmpty)
      result
    else
      BadRequest( "Invalid user id" )
  }

  def IsValidUserAndDevice(user_id : Long, device_id : Long)(f: Request[AnyContent] => Result) = Action { req =>
    withValidUser(user_id) {
      withValidDevice(device_id) {
        f(req)
      }
    }
}

That’s it! Let’s use it now in our controller to simplify the code. Can you guess what it would be?

object Users extends Controllers with Security {

  def attachDevice(user_id : Long, device_id : Long) = IsValidUserAndDevice(user_id, device_id) {
    if (models.User.attach(user_id, device_id))
      Ok("succeeded")
    else
      InternalServerError
  }
}

This is a lot cleaner isn’t it? But what if we want to process a POST request with form data? How would we use our validations? It turns out that it’s not so hard to do (see code below).

object Users extends Controllers {

  import play.api.data.Forms._
  import play.api.data._

  case class AttachData(user_id : Long, device_id : Long)

  val attachForm = Form(
    mapping(
      "user_id" -> longNumber,
      "device_id" -> longNumber
    )(AttachData.apply)(AttachData.unapply)
  )

  def attachDevice(user_id : Long, device_id : Long) = Action {
    shareForm.bindFromRequest.fold(
      formWithErrors => BadRequest( formWithErrors.errorsAsJson ),
      data => {
        withValidUser(data.user_id) {
          withValidDevice(data.device_id) {
            if (models.User.attach(user_id, device_id))
              Ok("succeeded")
            else
              InternalServerError
          }
        }
      }
    )
  }
}

We could for improvement define a method withValidUserAndDevice in our Security trait and then simplify the POST request code as well.

Conclusion

There is no very special conclusion telling the truth but I just wanted to point out this pattern with defining two validation methods:

  1. A method that returns an Action and can simplify the code of the GET requests
  2. A method that returns a Result which can simplify the POST requests and is flexible enough to be used in every action you want

As always, I hope you’ll find this blog entry useful and will help you simplify your validation code with Play Framework 2 and Scala 🙂

Advertisements

4 thoughts on “How to simplify your action’s validation code in Play Framework 2 (with Scala of course)

  1. Pingback: Advantages of Play Framework in Scala over PHP down the road | The adventures of a developer!
  2. Thank you for the great post. I was looking for quite some time for a good way to validate params in play, without using nested pattern matches. I’m not clear on your first point of the conclusion “A method that returns an Action and can simplify the code of the GET requests”. If that refers to the method IsValidUserAndDevice in your example, that seems to return a Result, not an Action. Also, in the code which defines the trait Security, on line 21, where is “result” coming from? Is it not supposed to be an application of the f function instead?

    • Hi Vlad!
      Thanks for your feedback! Glad to know that somebody read the blog post & found it useful 🙂

      Ok .. you are right .. I have a mistake in my code. It shouldn’t be “result” there but “f(req)” (will update the post in a moment). So this way if both device and user are valid, it’ll apply the request to the “f” function and return the result (which is of type Result).

      Now about the conclusion .. it does refer to IsValidUserAndDevice and it correctly states that it returns an Action. It seems like it returns a Result but the whole function is wrapped in an Action { req => /* code that returns Result */ }. Therefore, the IsValidUserAndDevice function returns an Action and in our controller code we don’t need to wrap it again with Action so the code becomes more clean 🙂

      Hope it makes a bit more sense now. Please let me know if it doesn’t 🙂
      Have a nice day!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s