22 fields limit in Scala 2.11 + Play Framework 2.3 Case classes and functions

后端 未结 8 1563
盖世英雄少女心
盖世英雄少女心 2020-12-08 03:05

Scala 2.11 is out and the 22 fields limit for case classes seems to be fixed (Scala Issue, Release Notes).

This has been an issue for me for a while because I use ca

8条回答
  •  粉色の甜心
    2020-12-08 03:09

    We were also breaking our models into multiple case classes, but this was quickly becoming unmanageable. We use Slick as our object relational mapper, and Slick 2.0 comes with a code generator that we use to generate classes (which come with apply methods and copy constructors to mimic case classes) along with methods to instantiate models from Json (we do not automatically generate methods to convert models into Json because we have too many special cases to deal with). Using the Slick code generator does not require you to use Slick as your object relational mapper.

    This is part of the input to the code generator - this method takes a JsObject and uses it to either instantiate a new model or update an existing model.

    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(", ")}))
        }
      })
    }
    

    For example, with our ActivityLog model this produces the following code. If "original" is None then this is being called from a "createFromJson" method and we instantiate a new model; if "original" is Some(activityLog) then this is being called from an "updateFromJson" method and we update the existing model. The "condenseUnit" method being called on the "val errs = ..." line takes a Seq[Try[Unit]] and produces a Try[Unit]; if the Seq has any errors then the Try[Unit] concatenates the exception messages. The parseJsonField and parseField methods are not generated - they're just referenced from the generated code.

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

提交回复
热议问题