Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly validate forms with Play! in Scala?

I'm brand new to Play!, and I'm trying to migrate my existing website from cakePHP to Play!.

The problem I'm facing is about form validation.

I defined a case class User, representing the users of my website :

case class User(
  val id: Long,
  val username: String,
  val password: String,
  val email: String
  val created: Date)

(There are some more fields, but these ones suffice to explain my problem)

I would like my users to be able to create an account on my website, using a form, and I would like this form to be validated by Play!.

So, I created the following action :

def register = Action {
  implicit request =>

    val userForm = Form(
      mapping(
        "id" -> longNumber,
        "username" -> nonEmptyText(8),
        "password" -> nonEmptyText(5),
        "email" -> email,
        "created" -> date)(User.apply)(User.unapply))

    val processedForm = userForm.bindFromRequest
    processedForm.fold(hasErrors => BadRequest("Invalid submission"), success => {
      Ok("Account registered.")
    })
}

Obviously, I do not want the user to fill the id or the creation date by himself in the form. So my question is : what should I do ?

Should I define a new "transition model" containing only the fields that are actually provided to the user in the form, and transform this intermediary model into the complete one before inserting it into my database ?

That is, replace my action by something like that :

def register = Action {
  implicit request =>

    case class UserRegister(
      username: String,
      password: String,
      email: String)

    val userForm = Form(
      mapping(
        "username" -> nonEmptyText(8),
        "password" -> nonEmptyText(8),
        "email" -> email)(UserRegister.apply)(UserRegister.unapply)

    val processedForm = userForm.bindFromRequest
    processedForm.fold(hasErrors => BadRequest("Invalid submission"), success => {
      val user = User(nextID, success.username, success.password, success.email, new Date())
      // Register the user...
      Ok("Account created")
}

Or is there another, cleaner way to do what I want ?

I've been through many tutorials and the book "Play for Scala", but in the only examples that I found, the models were fully filled by the forms... I really like Play! so far, but it looks like the documentation often lacks examples...

Thank you very much for your answers !

like image 943
Martin Avatar asked Oct 27 '13 19:10

Martin


1 Answers

You have a few choices:

Firstly, you could make the id and created fields Option[Long] and Option[Date] respectively. Then use a mapping like:

val userForm = Form(
  mapping(
    "id" -> optional(longNumber),
    "username" -> nonEmptyText(8),
    "password" -> nonEmptyText(5),
    "email" -> email,
    "created" -> optional(date)
  )(User.apply)(User.unapply)
)

That would, I think, be logical, since a User with a None id would indicate that it has not yet been saved. This works well when you want to use the same form mapping to update an existing record.

Alternately, you can use ignored mappings with some arbitrary placeholder data:

val userForm = Form(
  mapping(
    "id" -> ignored(-1L),
    "username" -> nonEmptyText(8),
    "password" -> nonEmptyText(5),
    "email" -> email,
    "created" -> ignored(new Date)
  )(User.apply)(User.unapply)
)

This is not so good when reusing the form for update operations!

Finally, don't forget that your form mappings are bound/filled by functions that turn a tuple into an object, and an object into a tuple respectively. It's just a convenient convention to use the case class User.apply and User.unapply methods since these do just that. You could write alternative factory methods on your User object to handle form instantiation:

object User {
  def formApply(username: String, password: String, email: String): User = 
    new User(-1L, username, password, email, new Date)

  def formUnapply(user: User): Option[(String,String,String)] = 
    Some((user.username, user.password, user.email))
}

And then user those in the Form object:

val userForm = Form(
  mapping(
    "username" -> nonEmptyText(8),
    "password" -> nonEmptyText(5),
    "email" -> email
  )(User.formApply)(User.formUnapply)
)

Also, it's worth noting that the Scala forms documentation is about to get much better in 2.2.1 (and in fact it might already have rolled out here).

like image 96
Mikesname Avatar answered Sep 27 '22 23:09

Mikesname