Límite de 22 campos en Scala 2.11+Play Framework 2.3 Clases y funciones de casos
playframework-2.3 (6)
Scala 2.11 está desactivado y el límite de 22 campos para clases de casos parece estar fijo ( Issue Scala , Release Notes ).
Esto ha sido un problema para mí desde hace un tiempo porque uso clases de casos para modelar entidades de bases de datos que tienen más de 22 campos en Play + Postgres Async . Mi solución en Scala 2.10 fue dividir los modelos en varias clases de casos, pero encuentro que esta solución es difícil de mantener y extender, y esperaba poder implementar algo como se describe a continuación después de cambiar a Play 2.3.0-RC1 + Scala 2.11. 0:
package entities
case class MyDbEntity(
id: String,
field1: String,
field2: Boolean,
field3: String,
field4: String,
field5: String,
field6: String,
field7: String,
field8: String,
field9: String,
field10: String,
field11: String,
field12: String,
field13: String,
field14: String,
field15: String,
field16: String,
field17: String,
field18: String,
field19: String,
field20: String,
field21: String,
field22: String,
field23: String,
)
object MyDbEntity {
import play.api.libs.json.Json
import play.api.data._
import play.api.data.Forms._
implicit val entityReads = Json.reads[MyDbEntity]
implicit val entityWrites = Json.writes[MyDbEntity]
}
El código anterior no se puede compilar con el siguiente mensaje para "Leer" y "Escribir":
No unapply function found
Actualizando las "Lecturas" y "Escrituras" a:
implicit val entityReads: Reads[MyDbEntity] = (
(__ / "id").read[Long] and
(__ / "field_1").read[String]
........
)(MyDbEntity.apply _)
implicit val postWrites: Writes[MyDbEntity] = (
(__ / "id").write[Long] and
(__ / "user").write[String]
........
)(unlift(MyDbEntity.unapply))
Tampoco funciona:
implementation restricts functions to 22 parameters
value unapply is not a member of object models.MyDbEntity
Tengo entendido que Scala 2.11 todavía tiene algunas limitaciones en las funciones y que algo como lo que describí anteriormente no es posible todavía. Esto me parece extraño ya que no veo el beneficio de eliminar las restricciones en las clases de casos si uno de los principales casos de usuarios aún no es compatible, entonces me pregunto si me está perdiendo algo.
¡Los indicadores de problemas o detalles de implementación son más que bienvenidos! ¡Gracias!
casos en los que las clases de casos podrían no funcionar; uno de estos casos es que las clases de casos no pueden tomar más de 22 campos. Otro caso puede ser que no se conoce el esquema de antemano. En este enfoque, los datos se cargan como un RDD de los objetos de fila. El esquema se crea por separado utilizando los objetos StructType y StructField, que representan una tabla y un campo, respectivamente. El esquema se aplica a la fila RDD para crear DataFrame en Spark .
También estábamos dividiendo nuestros modelos en varias clases de casos, pero esto se estaba volviendo rápidamente inmanejable. Usamos Slick como nuestro mapeador relacional de objetos, y Slick 2.0 viene con un generador de código que usamos para generar clases (que vienen con métodos de aplicación y constructores de copia para imitar clases de casos) junto con métodos para crear modelos de Json (no hacemos automáticamente generar métodos para convertir modelos en Json porque tenemos demasiados casos especiales para tratar). El uso del generador de código Slick no requiere que uses Slick como asignador relacional de objetos.
Esto es parte de la entrada al generador de código: este método toma un JsObject y lo usa para crear un nuevo modelo o para actualizar un modelo existente.
private def getItem(original: Option[${name}], json: JsObject, trackingData: TrackingData)(implicit session: scala.slick.session.Session): Try[${name}] = {
preProcess("$name", columnSet, json, trackingData).flatMap(updatedJson => {
${indent(indent(indent(entityColumnsSansId.map(c => s"""val ${c.name}_Parsed = parseJsonField[${c.exposedType}](original.map(_.${c.name}), "${c.name}", updatedJson, "${c.exposedType}")""").mkString("/n"))))}
val errs = Seq(${indent(indent(indent(indent(entityColumnsSansId.map(c => s"${c.name}_Parsed.map(_ => ())").mkString(", ")))))}).condenseUnit
for {
_ <- errs
${indent(indent(indent(indent(entityColumnsSansId.map(c => s"${c.name}_Val <- ${c.name}_Parsed").mkString("/n")))))}
} yield {
original.map(_.copy(${entityColumnsSansId.map(c => s"${c.name} = ${c.name}_Val").mkString(", ")}))
.getOrElse(${name}.apply(id = None, ${entityColumnsSansId.map(c => s"${c.name} = ${c.name}_Val").mkString(", ")}))
}
})
}
Por ejemplo, con nuestro modelo ActivityLog esto produce el siguiente código. Si "original" es None, se llama desde un método "createFromJson" y creamos una instancia de un nuevo modelo; si "original" es Some (activityLog), se llama a esto desde un método "updateFromJson" y actualizamos el modelo existente. El método "condenseUnit" que se llama en la línea "val errs = ..." toma un Seq [Try [Unit]] y produce un Try [Unit]; si el Seq tiene algún error, la Prueba [Unidad] concatena los mensajes de excepción. Los métodos parseJsonField y parseField no se generan; solo se hace referencia a ellos desde el código generado.
private def parseField[T](name: String, json: JsObject, tpe: String)(implicit r: Reads[T]): Try[T] = {
Try((json / name).as[T]).recoverWith {
case e: Exception => Failure(new IllegalArgumentException("Failed to parse " + Json.stringify(json / name) + " as " + name + " : " + tpe))
}
}
def parseJsonField[T](default: Option[T], name: String, json: JsObject, tpe: String)(implicit r: Reads[T]): Try[T] = {
default match {
case Some(t) => if(json.keys.contains(name)) parseField(name, json, tpe)(r) else Try(t)
case _ => parseField(name, json, tpe)(r)
}
}
private def getItem(original: Option[ActivityLog], json: JsObject, trackingData: TrackingData)(implicit session: scala.slick.session.Session): Try[ActivityLog] = {
preProcess("ActivityLog", columnSet, json, trackingData).flatMap(updatedJson => {
val user_id_Parsed = parseJsonField[Option[Int]](original.map(_.user_id), "user_id", updatedJson, "Option[Int]")
val user_name_Parsed = parseJsonField[Option[String]](original.map(_.user_name), "user_name", updatedJson, "Option[String]")
val item_id_Parsed = parseJsonField[Option[String]](original.map(_.item_id), "item_id", updatedJson, "Option[String]")
val item_item_type_Parsed = parseJsonField[Option[String]](original.map(_.item_item_type), "item_item_type", updatedJson, "Option[String]")
val item_name_Parsed = parseJsonField[Option[String]](original.map(_.item_name), "item_name", updatedJson, "Option[String]")
val modified_Parsed = parseJsonField[Option[String]](original.map(_.modified), "modified", updatedJson, "Option[String]")
val action_name_Parsed = parseJsonField[Option[String]](original.map(_.action_name), "action_name", updatedJson, "Option[String]")
val remote_ip_Parsed = parseJsonField[Option[String]](original.map(_.remote_ip), "remote_ip", updatedJson, "Option[String]")
val item_key_Parsed = parseJsonField[Option[String]](original.map(_.item_key), "item_key", updatedJson, "Option[String]")
val created_at_Parsed = parseJsonField[Option[java.sql.Timestamp]](original.map(_.created_at), "created_at", updatedJson, "Option[java.sql.Timestamp]")
val as_of_date_Parsed = parseJsonField[Option[java.sql.Timestamp]](original.map(_.as_of_date), "as_of_date", updatedJson, "Option[java.sql.Timestamp]")
val errs = Seq(user_id_Parsed.map(_ => ()), user_name_Parsed.map(_ => ()), item_id_Parsed.map(_ => ()), item_item_type_Parsed.map(_ => ()), item_name_Parsed.map(_ => ()), modified_Parsed.map(_ => ()), action_name_Parsed.map(_ => ()), remote_ip_Parsed.map(_ => ()), item_key_Parsed.map(_ => ()), created_at_Parsed.map(_ => ()), as_of_date_Parsed.map(_ => ())).condenseUnit
for {
_ <- errs
user_id_Val <- user_id_Parsed
user_name_Val <- user_name_Parsed
item_id_Val <- item_id_Parsed
item_item_type_Val <- item_item_type_Parsed
item_name_Val <- item_name_Parsed
modified_Val <- modified_Parsed
action_name_Val <- action_name_Parsed
remote_ip_Val <- remote_ip_Parsed
item_key_Val <- item_key_Parsed
created_at_Val <- created_at_Parsed
as_of_date_Val <- as_of_date_Parsed
} yield {
original.map(_.copy(user_id = user_id_Val, user_name = user_name_Val, item_id = item_id_Val, item_item_type = item_item_type_Val, item_name = item_name_Val, modified = modified_Val, action_name = action_name_Val, remote_ip = remote_ip_Val, item_key = item_key_Val, created_at = created_at_Val, as_of_date = as_of_date_Val))
.getOrElse(ActivityLog.apply(id = None, user_id = user_id_Val, user_name = user_name_Val, item_id = item_id_Val, item_item_type = item_item_type_Val, item_name = item_name_Val, modified = modified_Val, action_name = action_name_Val, remote_ip = remote_ip_Val, item_key = item_key_Val, created_at = created_at_Val, as_of_date = as_of_date_Val))
}
})
}
Estoy haciendo una biblioteca. por favor, intente esto https://github.com/xuwei-k/play-twenty-three
Puedes usar el módulo Scala de Jackson. La función json de Play se basa en Jackson scala. No sé por qué ponen un límite de campo de 22 aquí mientras que jackson admite más de 22 campos. Puede tener sentido que una llamada a función nunca pueda usar más de 22 parámetros, pero podemos tener cientos de columnas dentro de una entidad DB, por lo que esta restricción aquí es ridícula y hace que Play sea un juguete menos productivo. mira esto:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
object JacksonUtil extends App {
val mapper = new ObjectMapper with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
val t23 = T23("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w")
println(mapper.writeValueAsString(t23))
}
case class T23(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)
Probé la solución basada en Shapeless "Automatic Typeclass Derivation" propuesta en otra respuesta, y no funcionó para nuestros modelos: lanzaba excepciones de (clase de caso con ~ 30 campos y 4 colecciones anidadas de clases de casos con 4-10 campos).
Entonces, hemos adoptado esta solución y funcionó a la perfección. Confirmó que al escribir la prueba ScalaCheck. Tenga en cuenta que requiere Play Json 2.4.
Esto no es posible, listo para usar, por varias razones:
En primer lugar, como lo señaló gourlaysama , la biblioteca play-json usó scala macro para evitar el código de la placa base , y el código actual se basa en la
unapply
yapply
métodos para recuperar campos. Esto explica el primer mensaje de error en su pregunta.En segundo lugar, la biblioteca play-json se basa en una biblioteca funcional que actualmente solo funciona con un número fijo de parámetros correspondientes a los límites de arity de los campos de clases anteriores. Esto explica el segundo mensaje de error en su pregunta.
Sin embargo, es posible eludir el segundo punto ya sea:
usando la característica de Derivación automática de clases de datos sin forma . Naveen Gattu ha escrito una excelente idea haciendo lo correcto.
reemplazando el constructor funcional predeterminado
Primero, creando el FunctionalBuilder
faltante:
class CustomFunctionalBuilder[M[_]](canBuild: FunctionalCanBuild[M]) extends FunctionalBuilder {
class CustomCanBuild22[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22](m1: M[A1 ~ A2 ~ A3 ~ A4 ~ A5 ~ A6 ~ A7 ~ A8 ~ A9 ~ A10 ~ A11 ~ A12 ~ A13 ~ A14 ~ A15 ~ A16 ~ A17 ~ A18 ~ A19 ~ A20 ~ A21], m2: M[A22]) {
def ~[A23](m3: M[A23]) = new CustomCanBuild23[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23](canBuild(m1, m2), m3)
def and[A23](m3: M[A23]) = this.~(m3)
def apply[B](f: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22) => B)(implicit fu: Functor[M]): M[B] =
fu.fmap[A1 ~ A2 ~ A3 ~ A4 ~ A5 ~ A6 ~ A7 ~ A8 ~ A9 ~ A10 ~ A11 ~ A12 ~ A13 ~ A14 ~ A15 ~ A16 ~ A17 ~ A18 ~ A19 ~ A20 ~ A21 ~ A22, B](canBuild(m1, m2), { case a1 ~ a2 ~ a3 ~ a4 ~ a5 ~ a6 ~ a7 ~ a8 ~ a9 ~ a10 ~ a11 ~ a12 ~ a13 ~ a14 ~ a15 ~ a16 ~ a17 ~ a18 ~ a19 ~ a20 ~ a21 ~ a22 => f(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) })
def apply[B](f: B => (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22))(implicit fu: ContravariantFunctor[M]): M[B] =
fu.contramap(canBuild(m1, m2), (b: B) => { val (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) = f(b); new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(a1, a2), a3), a4), a5), a6), a7), a8), a9), a10), a11), a12), a13), a14), a15), a16), a17), a18), a19), a20), a21), a22) })
def apply[B](f1: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22) => B, f2: B => (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22))(implicit fu: InvariantFunctor[M]): M[B] =
fu.inmap[A1 ~ A2 ~ A3 ~ A4 ~ A5 ~ A6 ~ A7 ~ A8 ~ A9 ~ A10 ~ A11 ~ A12 ~ A13 ~ A14 ~ A15 ~ A16 ~ A17 ~ A18 ~ A19 ~ A20 ~ A21 ~ A22, B](
canBuild(m1, m2), { case a1 ~ a2 ~ a3 ~ a4 ~ a5 ~ a6 ~ a7 ~ a8 ~ a9 ~ a10 ~ a11 ~ a12 ~ a13 ~ a14 ~ a15 ~ a16 ~ a17 ~ a18 ~ a19 ~ a20 ~ a21 ~ a22 => f1(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) },
(b: B) => { val (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) = f2(b); new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(a1, a2), a3), a4), a5), a6), a7), a8), a9), a10), a11), a12), a13), a14), a15), a16), a17), a18), a19), a20), a21), a22) }
)
def join[A >: A1](implicit witness1: <:<[A, A1], witness2: <:<[A, A2], witness3: <:<[A, A3], witness4: <:<[A, A4], witness5: <:<[A, A5], witness6: <:<[A, A6], witness7: <:<[A, A7], witness8: <:<[A, A8], witness9: <:<[A, A9], witness10: <:<[A, A10], witness11: <:<[A, A11], witness12: <:<[A, A12], witness13: <:<[A, A13], witness14: <:<[A, A14], witness15: <:<[A, A15], witness16: <:<[A, A16], witness17: <:<[A, A17], witness18: <:<[A, A18], witness19: <:<[A, A19], witness20: <:<[A, A20], witness21: <:<[A, A21], witness22: <:<[A, A22], fu: ContravariantFunctor[M]): M[A] =
apply[A]((a: A) => (a: A1, a: A2, a: A3, a: A4, a: A5, a: A6, a: A7, a: A8, a: A9, a: A10, a: A11, a: A12, a: A13, a: A14, a: A15, a: A16, a: A17, a: A18, a: A19, a: A20, a: A21, a: A22))(fu)
def reduce[A >: A1, B](implicit witness1: <:<[A1, A], witness2: <:<[A2, A], witness3: <:<[A3, A], witness4: <:<[A4, A], witness5: <:<[A5, A], witness6: <:<[A6, A], witness7: <:<[A7, A], witness8: <:<[A8, A], witness9: <:<[A9, A], witness10: <:<[A10, A], witness11: <:<[A11, A], witness12: <:<[A12, A], witness13: <:<[A13, A], witness14: <:<[A14, A], witness15: <:<[A15, A], witness16: <:<[A16, A], witness17: <:<[A17, A], witness18: <:<[A18, A], witness19: <:<[A19, A], witness20: <:<[A20, A], witness21: <:<[A21, A], witness22: <:<[A22, A], fu: Functor[M], reducer: Reducer[A, B]): M[B] =
apply[B]((a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10, a11: A11, a12: A12, a13: A13, a14: A14, a15: A15, a16: A16, a17: A17, a18: A18, a19: A19, a20: A20, a21: A21, a22: A22) => reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.unit(a1: A), a2: A), a3: A), a4: A), a5: A), a6: A), a7: A), a8: A), a9: A), a10: A), a11: A), a12: A), a13: A), a14: A), a15: A), a16: A), a17: A), a18: A), a19: A), a20: A), a21: A), a22: A))(fu)
def tupled(implicit v: VariantExtractor[M]): M[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)] =
v match {
case FunctorExtractor(fu) => apply { (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10, a11: A11, a12: A12, a13: A13, a14: A14, a15: A15, a16: A16, a17: A17, a18: A18, a19: A19, a20: A20, a21: A21, a22: A22) => (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) }(fu)
case ContravariantFunctorExtractor(fu) => apply[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)] { (a: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)) => (a._1, a._2, a._3, a._4, a._5, a._6, a._7, a._8, a._9, a._10, a._11, a._12, a._13, a._14, a._15, a._16, a._17, a._18, a._19, a._20, a._21, a._22) }(fu)
case InvariantFunctorExtractor(fu) => apply[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)]({ (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10, a11: A11, a12: A12, a13: A13, a14: A14, a15: A15, a16: A16, a17: A17, a18: A18, a19: A19, a20: A20, a21: A21, a22: A22) => (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) }, { (a: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)) => (a._1, a._2, a._3, a._4, a._5, a._6, a._7, a._8, a._9, a._10, a._11, a._12, a._13, a._14, a._15, a._16, a._17, a._18, a._19, a._20, a._21, a._22) })(fu)
}
}
class CustomCanBuild23[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23](m1: M[A1 ~ A2 ~ A3 ~ A4 ~ A5 ~ A6 ~ A7 ~ A8 ~ A9 ~ A10 ~ A11 ~ A12 ~ A13 ~ A14 ~ A15 ~ A16 ~ A17 ~ A18 ~ A19 ~ A20 ~ A21 ~ A22], m2: M[A23]) {
}
}
y luego al proporcionar su propia instancia de FunctionalBuilderOps
:
implicit def customToFunctionalBuilderOps[M[_], A](a: M[A])(implicit fcb: FunctionalCanBuild[M]) = new CustomFunctionalBuilderOps[M, A](a)(fcb)
Finalmente, con respecto al primer punto, he enviado una solicitud de extracción para tratar de simplificar la implementación actual.