Play! framework 2.0: Validate field in forms using other fields

匿名 (未验证) 提交于 2019-12-03 02:56:01

问题:

In the play! framework, using scala,say that i have a form such as follows:

import play.api.data._ import play.api.data.Forms._ import play.api.data.validation.Constraints._  case class User(someStringField: String, someIntField: Int)  val userForm = Form(   mapping(     "someStringField" -> text,     "someIntField" -> number verifying(x => SomeMethodThatReceivesAnIntAndReturnsABoolean(x))   )(User.apply)(User.unapply)

)

where SomeMethodThatReceivesAnIntAndReturnsABoolean is a method that performs some logic on the int to validate it.

However, i would like to be able to consider the value of the someStringField when validating the someIntField, is there a way to achieve this in play framework's forms? I know that i can do something like:

val userForm = Form(   mapping(     "someStringField" -> text,     "someIntField" -> number    )(User.apply)(User.unapply) .verifying(x => SomeFunctionThatReceivesAnUserAndReturnsABoolean(x))

and then i would have the entire user instance available passed to the validation function. The problem with that approach is that the resulting error would be associated with the entire form instead of being associated with the someIntField field.

Is there a way to get both things, validate a field using another field and maintain the error associated to the specific field i wish to validate, instead of the entire form?

回答1:

val userForm = Form( mapping(   "id" -> optional(longNumber),   "surename" -> text,   "forename" -> text,   "username" -> text,   "age" -> number )(User.apply)(User.unapply) )  private def validateForm(form:Form[User]) = {   if(form("username").value.get == "tom" || form("age").value.get == "38") {     form       .withError("forename", "tom - forename error")       .withError("surename", "tom - surename error")   }   else     form }  def update = Action { implicit request =>   userForm.bindFromRequest.fold({     formWithErrors => BadRequest(users.edit(validateForm(formWithErrors)))   }, { user =>      val theForm = validateForm(userForm.fill(user))     if(theForm.hasErrors) {       BadRequest(users.edit(theForm))     } else {       Users.update(user)       Redirect(routes.UsersController.index).flashing("notice" -> s"${user.forename} updated!")     }   })  } 

EDIT: Use a custom play.api.data.format.Formatter in idiomatic play, more on http://workwithplay.com/blog/2013/07/10/advanced-forms-techniques/ - this lets you programmatically add errors to a form. My Formatter looks like this:

val usernameFormatter = new Formatter[String] {  override def bind(key: String, data: Map[String, String]): Either[Seq[FormError], String] = {   // "data" lets you access all form data values   val age = data.get("age").get   val username = data.get("username").get   if(age == "66") {     Left(List(FormError("username", "invalid"), FormError("forename", "invalid")))   } else {     Right(username)   } }  override def unbind(key: String, value: String): Map[String, String] = {   Map(key -> value) } } }

Registered in the form mapping like this:

mapping( [...]   "username" -> of(usernameFormatter), [....]


回答2:

I believe what you're looking for is play.api.data.validation.Constraint.

Say you have a RegisterForm with a list of predefined cities and an otherCity field and you need either the cities or otherCity to be supplied, i.e., otherCity should be validated if cities is not provided:

case class RegisterForm(   email: String,   password: String,   cities: Option[List[String]],   otherCity: Option[String] )

You can write a custom Constraint around this:

val citiesCheckConstraint: Constraint[RegisterForm] = Constraint("constraints.citiescheck")({   registerForm =>     // you have access to all the fields in the form here and can     // write complex logic here     if (registerForm.cities.isDefined || registerForm.otherCity.isDefined) {       Valid     } else {       Invalid(Seq(ValidationError("City must be selected")))     } })

And your form definition becomes:

val registerForm = Form(   mapping(     "email" -> nonEmptyText.verifying(emailCheckConstraint),     "password" -> nonEmptyText.verifying(passwordCheckConstraint),     "cities" -> optional(list(text)),     "other_city" -> optional(text)   )(RegisterForm.apply)(RegisterForm.unapply).verifying(citiesCheckConstraint) )

In this example emailCheckConstraint and passwordCheckConstraint are additional custom constraints that I defined similar to citiesCheckConstraint. This works in Play 2.2.x.

UPDATE: Works on Play 2.3.8 as well.



回答3:

if you don't mind having a prefix for you params you can group the related params:

val aForm = Form( mapping(   "prefix" -> tuple(     "someStringField" -> text,     "someIntField" -> number   ) verifying (tup => your verification) )(tup => User.apply(tup._1, tup._2)(User.unapply...)

I use something similar just without the surrounding mapping. You will have to adjust the apply/unapply a little and pass the arguments manually for it to compile.

The error will be registered to the "prefix" group.

I also find it weird that you cannot register errors on any field you'd like using FormError when verifying the form...



回答4:

Thanks to Tom Myer, Here what I used

class MatchConstraint[A](val targetField:String, val map:(String, Map[String, String]) => A, val unmap:A => String) extends Formatter[A] {   override def bind(key: String, data: Map[String, String]): Either[Seq[FormError], A] = {     val first = data.getOrElse(key, "")     val second = data.getOrElse(targetField, "")     if (first == "" || !first.equals(second)) {       Left(List(FormError(key, "Not Match!")))     }     else {       Right(map(key, data))     }   }    override def unbind(key: String, value: A): Map[String, String] = Map(key -> unmap(value)) }

And here what's my form look like

val registerForm = Form(   mapping(     "email" -> email.verifying(minLength(6)),     "password" -> text(minLength = 6),     "passwordConfirmation" -> of(new MatchConstraint[String]("password", (key, data) => data.getOrElse(key, ""), str => str))   )(RegisterData.apply)(RegisterData.unapply) )


回答5:

I guess that they map the scala-code to JSR-Validation. There it's definitely not possible. There are some arguments to do this. Mainly that a validation should be simple and not make complex logic. How ever I still miss this too. OVal from play1 was better for me.



回答6:

In the documentation: Playframework Documentation

You can see the following code: val userFormConstraintsAdHoc = Form( mapping( "name" -> text, "age" -> number )(UserData.apply)(UserData.unapply) verifying("Failed form constraints!", fields => fields match { case userData => validate(userData.name, userData.age).isDefined }) )

Mainly just verify after the unapply and you have all the fields mapped, so you can make a more complete validation.



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!