Play Framework Form只有18个参数

时间:2012-06-14 06:25:30

标签: scala playframework playframework-2.0

我观察到,当我向Play Framework Form-class添加超过18个参数时,我得到一个很长的(并且对我来说难以理解)编译错误。

这是一个记录在案的限制吗?我需要在表单帖子中接收多达29个参数。我没有决定参数的设计和数量,因为我正在使用开放标准实现协议。

我是这样的映射:

val registration = Form(mapping(
    "client_type" -> nonEmptyText,
    "client_id" -> optional(nonEmptyText),
    ... up to 29 args, all optional(nonEmptyText)
    ){ (clientType, clientId ...) => RegistrationRequest(clientType, clientId ...) }
     { req => None })

我的策略是以这种方式进行映射,而不是应用/取消应用,并创建一个案例类的层次结构。原因是在Case类中解决了22个参数限制,这是我遇到的第一个看似任意的限制。最多18个args映射工作,之后我得到一个很长的编译错误。

可在此处找到错误消息(包含的时间太长):https://gist.github.com/2928297

我正在寻找有关如何解决此限制的建议。我知道在Post表单中发送29个参数是不好的设计,但它仍然是可能的。


哈克/解决方法/解决方案

好的,这是我的黑客一起解决方法(写这篇文章比实施花了更长的时间,我在这上面砍了大约30分钟)

我编写了预处理请求参数的函数,并添加了一个组前缀来对某些参数进行分组。然后我使用生成的Map [String,String]并继续使用表单类进行处理,像往常一样进行验证等。这允许我在映射中使用嵌套的case类,并且低于18 params限制。

谨防:未来的丑陋代码!我可能不会像这样显示早期的hacky代码,但我希望它能帮助其他想要解决方法的人。

def preprocessFormParams(prefix:String, replace:String)(implicit request:Request[AnyContent]):Map[String, String] = request.body.asFormUrlEncoded.map( _.filterKeys( _.startsWith(prefix)).map( m => m._1.patch(0, replace, prefix.length)  -> m._2.head )).getOrElse(Map.empty)
def unprocessedFormParams(prefixes:Set[String])(implicit request:Request[AnyContent]):Map[String, String] = request.body.asFormUrlEncoded.map( _.filterKeys( !prefixes.contains(_) ).map( m => m._1 -> m._2.head )).getOrElse(Map.empty)

所以这些功能应该是为了理解或分手,但是这里有: preprocessedFormParms获取前缀并替换它:

val clientParams = preprocessFormParams("client_", "client.")
("client_id" -> "val1", "client_type" -> "val2") becomes ("client.id" -> "val1", "client.type" -> "val2")

当我有group.key1,group.key2形式的参数时,我可以将case类嵌套在这样的表单中

Form(mapping("client" -> mapping("type" -> nonEmptyText
    "id" -> optional(nonEmptyText),
    "secret" -> optional(nonEmptyText))
    (RegisterClient.apply)(RegisterClient.unapply)
    ... more params ...)
    (RegisterRequest.apply)(RegisterRequest.unapply)

在我的行动中,我会过滤掉我的每个小组

implicit request =>
val clientParams = preprocessFormParams("client_", "client.")       
val applicationParams = preprocessFormParams("application_", "application.")
val unprocessedParams = unprocessedFormParams(Set("client_", "application_"))
val processedForm = clientParams ++ applicationParams ++ unprocessedParams

最后,我可以像平常一样应用我的表单,但现在我得到了嵌套结构I,它减少了参数的数量,并希望使案例类更易于管理。

clientRegistrationForm.bind(processedForm).fold( ... )

使用此方法可以减少参数数量。如果你的参数不像我的问题那样容易分组,那么你仍然可以使用相同的基本方法,但过滤其他标准。

3 个答案:

答案 0 :(得分:4)

您使用的mapping方法不是单一方法,但会重载。对于单个参数,它有两个类型参数,一个用于结果类型,另一个用于您正在使用的元素。它构造了一个ObjectMapping1。对于两个参数,它有三个类型参数,它构造一个ObjectMapping2

正如您所注意到的,这些ObjectMappingX类定义为ObjectMapping18。你可以在Play的源代码中找到它 play/api/data/Forms.scala

建议的解决方案是避免使用此大小的非嵌套表单。如果这是不可避免的,您可以使用与内置Play游戏不同的库,也可以定义缺少的ObjectMappingX对象和相应的方法来自己构建它们。

答案 1 :(得分:3)

几周前,我就这个问题发表了opened a ticket

如果你投票支持它,也许它会从Play开发者那里看一下。

怀疑它在他们的优先级列表上是否很高(不幸的是,它或多或少只是在19,20,21和22映射[T]上复制粘贴)

如果你绝望,你可以分叉玩;否则,想出一个解决方法,例如,利用嵌套表格或拆分>将22场模型分成不同的形式。

答案 2 :(得分:1)

前几天我不得不解决这个限制,并没有找到这个S.O的帖子,并提出了一种不同的方法,尽管它看起来有些不稳定但似乎有效。

我们的表单组件

import play.api.data.Form
import play.api.data.Forms._

case class P1_18(f1: String,f2: String,f3: String,f4: String,f5: String,f6: String,f7: String,f8: String,f9: String,f10: String,f11: String,f12: String,f13: String,f14: String,f15: String,f16: String,f17: String,f18: String)

case class P2_18(f1: String,f2: String,f3: String,f4: String,f5: String,f6: String,f7: String,f8: String,f9: String,f10: String,f11: String,f12: String,f13: String,f14: String,f15: String,f16: String,f17: String,f18: String)

case class P36(f1: String,f2: String,f3: String,f4: String,f5: String,f6: String,f7: String,f8: String,f9: String,f10: String,f11: String,f12: String,f13: String,f14: String,f15: String,f16: String,f17: String,f18: String,f19: String,f20: String,f21: String,f22: String,f23: String,f24: String,f25: String,f26: String,f27: String,f28: String,f29: String,f30: String,f31: String,f32: String,f33: String,f34: String,f35: String,f36: String)

P36是你真正想要的对象,P1 / P2只是你在框架的约束下用来构建它的类,我在实际的应用程序中将它们设置为包装表单的对象。

然后我们有了表单定义,这就是魔术发生的地方:

val f = Form(
    mapping(
    "" -> mapping(
        "f1" -> text,
        "f2" -> text,
        "f3" -> text,
        "f4" -> text,
        "f5" -> text,
        "f6" -> text,
        "f7" -> text,
        "f8" -> text,
        "f9" -> text,
        "f10" -> text,
        "f11" -> text,
        "f12" -> text,
        "f13" -> text,
        "f14" -> text,
        "f15" -> text,
        "f16" -> text,
        "f17" -> text,
        "f18" -> text
    )(P1_18.apply)(P1_18.unapply),
    "" -> mapping(
        "f19" -> text,
        "f20" -> text,
        "f21" -> text,
        "f22" -> text,
        "f23" -> text,
        "f24" -> text,
        "f25" -> text,
        "f26" -> text,
        "f27" -> text,
        "f28" -> text,
        "f29" -> text,
        "f30" -> text,
        "f31" -> text,
        "f32" -> text,
        "f33" -> text,
        "f34" -> text,
        "f35" -> text,
        "f36" -> text
    )(P2_18.apply)(P2_18.unapply)
    )(
    (p1, p2) =>
        P36(
            f1 = p1.f1,
            f2 = p1.f2,
            f3 = p1.f3,
            f4 = p1.f4,
            f5 = p1.f5,
            f6 = p1.f6,
            f7 = p1.f7,
            f8 = p1.f8,
            f9 = p1.f9,
            f10 = p1.f10,
            f11 = p1.f11,
            f12 = p1.f12,
            f13 = p1.f13,
            f14 = p1.f14,
            f15 = p1.f15,
            f16 = p1.f16,
            f17 = p1.f17,
            f18 = p1.f18,
            f19 = p2.f1,
            f20 = p2.f2,
            f21 = p2.f3,
            f22 = p2.f4,
            f23 = p2.f5,
            f24 = p2.f6,
            f25 = p2.f7,
            f26 = p2.f8,
            f27 = p2.f9,
            f28 = p2.f10,
            f29 = p2.f11,
            f30 = p2.f12,
            f31 = p2.f13,
            f32 = p2.f14,
            f33 = p2.f15,
            f34 = p2.f16,
            f35 = p2.f17,
            f36 = p2.f18
        )
    )(
        p => {
            val p1 = P1_18(p.f1,p.f2,p.f3,p.f4,p.f5,p.f6,p.f7,p.f8,p.f9,p.f10,p.f11,p.f12,p.f13,p.f14,p.f15,p.f16,p.f17,p.f18)
            val p2 = P2_18(p.f19,p.f20,p.f21,p.f22,p.f23,p.f24,p.f25,p.f26,p.f27,p.f28,p.f29,p.f30,p.f31,p.f32,p.f33,p.f34,p.f35,p.f36)
            Option(
                (p1,p2)
            )
        }
    )
)

你可能会说:嗯。呃,对不起,你有两个空键。怎么可能有用呢?我说:

val dataSeq = for(i <- 1 to 36) yield s"f${i}" -> s"text no. #${i}"
val filledFormFromMap = f.bind(dataSeq.toMap)

filledFormFromMap.value

// res9: Option[P36] = Some(P36(text no. #1,text no. #2,text no. #3,text no. #4,text no. #5,text no. #6,text no. #7,text no. #8,text no. #9,text no. #10,text no. #11,text no. #12,text no. #13,text no. #14,text no. #15,text no. #16,text no. #17,text no. #18,text no. #19,text no. #20,text no. #21,text no. #22,text no. #23,text no. #24,text no. #25,text no. #26,text no. #27,text no. #28,text no. #29,text no. #30,text no. #31,text no. #32,text no. #33,text no. #34,text no. #35,text no. #36))

它实际上确实可以正常工作。 18对象映射限制的问题不在于表单不能支持超过18个字段内部,但绑定无法支持它。但是,当我查看ObjectMapping source时,我注意到默认情况下,ObjectMapping的key是一个空字符串。并且字段绑定与给定的前缀绑定,然后与所述前缀绑定:

val field1 = f1._2.withPrefix(f1._1).withPrefix(key)

这让我意识到&#34; top&#34;表单只是一个空键。除了猖獗的好奇之外没有任何理由我用两个空键试了一下,因为你可以看到in ObjectMapping 2两个字段都使用了空键:

  val field1 = f1._2.withPrefix(f1._1).withPrefix(key)

  val field2 = f2._2.withPrefix(f2._1).withPrefix(key)

由于mappings中的Mapping字段只是一个Seq[Mapping],我认为在深层合并方法下面的所有内容以及我们未使用地图的地方密钥会发生冲突,但它们会以非破坏性的方式组合,因为它们都共享这个顶级密钥,这就是我相信如何根据嵌套映射的方式生成field.nested.thing映射他们自己。总而言之,这意味着您可以对同一个键(或者至少是空字符串)进行多次绑定,因此可以通过将其分解为更小的组件然后提供手动来构造大于18个字段的任何内容。 {1}}和apply方法结合起来(反对尝试使用P36.apply和P36.unapply,因为那些因为元组限制而不能工作我相信)