19

在剧中!框架,使用 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)

)

whereSomeMethodThatReceivesAnIntAndReturnsABoolean是一种在 int 上执行一些逻辑来验证它的方法。

但是,我希望能够someStringField在验证时考虑 的价值someIntField,有没有办法在游戏框架的形式中实现这一点?我知道我可以做类似的事情:

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

然后我会将可用的整个用户实例传递给验证函数。这种方法的问题是产生的错误将与整个表单相关联,而不是与someIntField字段相关联。

有没有办法得到这两个东西,使用另一个字段验证一个字段并维护与我希望验证的特定字段相关的错误,而不是整个表单?

4

6 回答 6

8

我对根据其他字段的值向字段添加验证有相同的要求。我不确定这是如何在惯用的 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/ - 这可以让您以编程方式将错误添加到表单。我的格式化程序如下所示:

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),
[....]
于 2013-11-09T15:47:31.767 回答
7

我相信您正在寻找的是play.api.data.validation.Constraint.

假设您有一个RegisterForm带有预定义列表cities和一个otherCity字段的列表,并且您需要提供citiesor otherCity,即,如果未提供,otherCity则应进行验证:cities

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

您可以围绕此编写自定义约束:

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)
)

在此示例中emailCheckConstraintpasswordCheckConstraint是我定义的其他自定义约束,类似于citiesCheckConstraint. 这适用于 Play 2.2.x。

更新: 也适用于 Play 2.3.8。

于 2015-03-25T04:55:44.667 回答
3

如果您不介意为参数添加前缀,则可以对相关参数进行分组:

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

我使用类似的东西,只是没有周围的映射。您将不得不稍微调整应用/取消应用并手动传递参数以进行编译。

错误将被注册到“前缀”组。

我还觉得奇怪的是,在验证表单时,您无法在任何您想使用 FormError 的字段上注册错误......

于 2013-08-02T22:41:45.937 回答
2

感谢 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)
)
于 2015-01-20T06:50:40.823 回答
0

在文档中: 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 之后验证,所有的字段都映射好了,这样可以做一个更完整的验证。

于 2015-03-09T08:54:55.260 回答
0

我猜他们将scala-code映射到JSR-Validation。那里绝对不可能。有一些论据可以做到这一点。主要是验证应该简单而不是复杂的逻辑。我怎么还是想念这个。play1 的 OVal 对我来说更好。

于 2012-08-28T09:52:30.060 回答