没有泛型支持的嵌套对象:变通方法

时间:2012-08-10 14:17:46

标签: forms scala generics playframework

由于播放模板图层中缺少通用支持而遇到了捕获22的情况。

我有几个购物车屏幕都需要用户和付款+可选的自定义字段。

case class Conference(
  user: User,
  payment: Payment
  ... custom fields here
)

因此,我没有复制每个购物车模型的所有用户和付款表单字段,而是按上述方式进行了整合并实现了嵌套表单。

现在,问题发生在模板层,其中迄今为止还没有支持泛型。

父/容器表单引入嵌套的子表单,如下所示:

@(_form: Form[Conference])

@user.nested( UserForm.form.fill(_form.get.user) )
@payment.nested( PaymentForm.form.fill(_form.get.payment) )

然后子用户表单如下所示:

@(_form: Form[User])

@inputText(_form("user.firstName"), '_label-> "First Name*", 'class-> "required")
@inputText(_form("user.lastName"), '_label-> "Last Name*", 'class-> "required")
...

和用户模型:

case class User(firstName: String, lastName: String ...)

当用户模型中没有用户属性时,如何访问“user.firstName”,“user.lastName”等? Play Form的申请方法是:

def apply(key: String): Field = Field(
    this,
    key,
    constraints.get(key).getOrElse(Nil),
    formats.get(key),
    errors.collect { case e if e.key == key => e },
    data.get(key))

基本上它会寻找属性data.user.firstName,这显然不起作用。

我考虑过向用户模型添加用户属性:

case class User(firstName: String, lastName: String ...) {
  val user: User
}

但不确定这是否有效和/或对案例类伴随对象应用/取消应用造成严重破坏。

无论如何,鉴于缺乏仿制药,这个问题的可行解决方案是什么?

如果支持泛型我们可以传递上限,一切都会很乐观:

trait CartOrder {
  user: User,
  payment: Payment
}
case class Conference(...) extends CartOrder

然后嵌套的User表单传递一个包含用户属性的实例,我们很好

@[T <: CartOrder](_form: Form[T])
@inputText(_form("user.firstName"), '_label-> "First Name*", 'class-> "required")
...

3 个答案:

答案 0 :(得分:2)

如果不考虑类型安全性(Form并非所有类型都安全),您可以使用Form[_]作为嵌套模板的参数类型。


如果你确实需要类型安全,一个选项是为Form创建一个包装类,它是协变的,并使用它来代替Form。一个实现是:

package views.html

import play.api.data._
import play.api.libs.json.JsValue

object FormView {
    implicit def formToFormView[A, T >: A](form: Form[A]): FormView[T] = new FormView[T] {
        type F = A
        def realForm = form
    }
}

trait FormView[+T] {
    type F <: T

    def realForm: Form[F]

    def apply(key: String): Field = realForm(key)

    def constraints : Map[String, Seq[(String, Seq[Any])]] = realForm.constraints

    def data: Map[String, String] = realForm.data

    def error(key: String): Option[FormError] = realForm.error(key)

    def errors(key: String): Seq[FormError] = realForm.errors(key)

    def errors: Seq[FormError] = realForm.errors

    def errorsAsJson: JsValue = realForm.errorsAsJson

    def get: T = realForm.get

    def formats: Map[String, (String, Seq[Any])] = realForm.formats

    def globalError: Option[FormError] = realForm.globalError

    def globalErrors: Seq[FormError] = realForm.globalErrors

    def hasErrors: Boolean = realForm.hasErrors

    def hasGlobalErrors: Boolean = realForm.hasGlobalErrors

    override def hashCode: Int = realForm.hashCode

    def mapping: Mapping[F] = realForm.mapping

    def value: Option[T] = realForm.value
}

现在而不是你的模板

@(_form: Form[CartOrder])

由于不变性而无效,您可以使用

@(_form: FormView[CartOrder])

您可以直接传入任何Form[T],其中TCartOrder的子类型

@user.nested(_form)

implicits将处理从Form到FormView的转换

可以在https://github.com/thatsmydoing/formtest

找到完整的示例

答案 1 :(得分:1)

好的,因此,如果你有更好的方式,那么就会出现这种情况:

play.api.data.Form [T]是不变的,因此没有骰子将超级会议(即CartOrder)传递给用户表单。换句话说,这会爆发:

// user.scala.html
@(_form: Form[CartOrder])

基本上你必须传入一个本身就是Form mappable的实例。

要解决模板层的乐趣,我已经实现了以下hack:

case class CartModel(user: User, payment: Payment)

修改
变得更好,在bind帮助器中添加到下面的CartForm映射器中,这使得视图中的语法清晰

object CartForm {
  import play.api.data.Forms._  
  val form = 
    Form( mapping(
      'user -> UserForm.mapper,
      'payment -> PaymentForm.mapper )(CartModel.apply)(CartModel.unapply) )

  // hey, generics!
  def bind[T <: Form[_ <: CartContract]](t: T) =
    t.value map{x=> form.fill( CartModel(x.user, x.payment) )} getOrElse form
}

然后在会议父表单中,拉入用户表单字段,如下所示:

@user.nested( CartForm.bind(_form) )

然后用户表单收到:

@(_form: Form[CartModel])

一般来说,嵌套表格会消除大量的样板,总体而言,进步。很高兴不依赖中间表格映射器,但这是我现在能想到的那么好......

答案 2 :(得分:0)

猜猜你想要

@(_form: Form[_ <: CartOrder])

而不是建议

@[T <: CartOrder](_form: Form[T])