Играть! framework 2.0: проверка поля в формах с использованием других полей
В игре! framework, используя scala, скажем, что у меня есть такая форма, как:
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)
)
где SomeMethodThatReceivesAnIntAndReturnsABoolean
- это метод, который выполняет некоторую логику для int, чтобы проверить его.
Тем не менее, я хотел бы иметь возможность рассмотреть значение someStringField
при проверке someIntField
, есть ли способ достичь этого в формах форм игры? Я знаю, что могу сделать что-то вроде:
val userForm = Form(
mapping(
"someStringField" -> text,
"someIntField" -> number
)(User.apply)(User.unapply)
.verifying(x => SomeFunctionThatReceivesAnUserAndReturnsABoolean(x))
а затем я получил бы весь пользовательский экземпляр, доступный для функции проверки. Проблема с этим подходом заключается в том, что полученная ошибка будет связана со всей формой, а не с полем someIntField
.
Есть ли способ получить обе вещи, проверить поле с использованием другого поля и сохранить ошибку, связанную с конкретным полем, которое я хочу проверить, а не всю форму?
Ответы
Ответ 1
У меня одинаковые требования с добавлением проверки на поля в зависимости от значения других полей. Я не уверен, как это делается в идиоматическом PLAY 2.2.1, но я придумал следующее решение. В этом использовании я унижаю встроенное "сопоставление" в простой конвертер типов и применяю проверку "расширенного межповерочного поля" в методе "validateForm". Отображение:
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!")
}
})
}
Несмотря на то, что это работает, я срочно ищу более идиоматическую версию...
РЕДАКТИРОВАТЬ: Используйте пользовательский файл play.api.data.format.Formatter в идиоматической игре, больше на http://workwithplay.com/blog/2013/07/10/advanced-forms-techniques/ - это позволяет вы программно добавляете ошибки в форму. Мой Formatter выглядит следующим образом:
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)
}
}
}
Зарегистрировано в форме, как показано ниже:
mapping(
[...]
"username" -> of(usernameFormatter),
[....]
Ответ 2
Я считаю, что вы ищете play.api.data.validation.Constraint
.
Предположим, что у вас есть RegisterForm
со списком предопределенных полей cities
и otherCity
, и вам нужно либо указать cities
, либо otherCity
, то есть otherCity
следует проверить, если cities
не предоставляется:
case class RegisterForm(
email: String,
password: String,
cities: Option[List[String]],
otherCity: Option[String]
)
Вы можете написать Constraint вокруг этого:
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")))
}
})
И ваше определение формы будет:
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)
)
В этом примере emailCheckConstraint
и passwordCheckConstraint
- дополнительные пользовательские ограничения, которые я определил как citiesCheckConstraint
. Это работает в Play 2.2.x.
UPDATE:
Работает и с Play 2.3.8.
Ответ 3
если вы не против иметь префикс для своих параметров, вы можете сгруппировать связанные параметры:
val aForm = Form(
mapping(
"prefix" -> tuple(
"someStringField" -> text,
"someIntField" -> number
) verifying (tup => your verification)
)(tup => User.apply(tup._1, tup._2)(User.unapply...)
Я использую нечто подобное просто без окружающего отображения.
Вам нужно будет немного изменить параметр apply/unapply и передать аргументы вручную для его компиляции.
Ошибка будет зарегистрирована в группе "префикс".
Я также считаю странным, что вы не можете регистрировать ошибки в любом поле, которое хотите использовать FormError при проверке формы...
Ответ 4
Спасибо Tom Myer, вот что я использовал
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))
}
И вот какая моя форма выглядит как
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
Я предполагаю, что они сопоставляют код scala с JSR-Validation. Там это определенно невозможно. Для этого есть несколько аргументов. В основном, что валидация должна быть простой и не создавать сложную логику. Как бы то ни было, я все еще скучаю по этому поводу. OVal из play1 был лучше для меня.
Ответ 6
В документации:
Документация по Playframework
Вы можете увидеть следующий код:
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
})
)
В основном просто проверяйте после unapply, и у вас есть все сопоставленные поля, поэтому вы можете сделать более полную проверку.