Подтвердить что ты не робот

Играть! 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.

Есть ли способ получить обе вещи, проверить поле с использованием другого поля и сохранить ошибку, связанную с конкретным полем, которое я хочу проверить, а не всю форму?

4b9b3361

Ответ 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, и у вас есть все сопоставленные поля, поэтому вы можете сделать более полную проверку.