Передача разных объектных моделей в качестве параметра методу в scala

Я действительно боролся с связями типов в scala и с тем, как их эффективно использовать. В настоящее время я пытаюсь понять, как я буду использовать их для редактирования только определенных полей в коллекции Mongo. Это означает передачу определенного объекта, содержащего только те поля, методу, который (после прочтения об отклонениях) Я подумал, что можно сделать так:

   abstract class DocClass
   case class DocPart1(oId: Option[BSONObjectID], name: String, other: String) extends DocClass
   case class DocPart2(city: String, country: String) extends DocClass

С методом, который вызывает более общий метод, например:

   def updateMultipleFields(oId: Option[BSONObjectID], dataModel: DocClass): Future[Result] = serviceClientDb.updateFields[T](collectionName, dataModel, oId)
   // updateFields updates the collection by passing *dataModel* into the collection, i.e. Json.obj("$set" -> dataModel)

Таким образом, dataModel может быть объектом DocPart1 или DocPart2. Я не хочу использовать параметр типа в updateMultipleFields (так как это интересная статья может предложить), так как это приводит к дополнительным проблемам при передаче их этому методу в других файлах проекта. Я делаю это, чтобы соблюдать DRY и поддерживать эффективные операции с базой данных.

Я ходил кругами с этим - может ли кто-нибудь пролить свет на это?

Отредактировано после комментариев @SerGr

Итак, чтобы быть полностью ясным; Я использую Play/Scala/ReactiveMongo Play JSON (как задокументировано здесь) и у меня есть коллекция MongoDB с большим количеством полей.

   case class Doc(oId: Option[BSONObjectID], name: String, city: String, country: String, city: String, continent: String, region: String, region: String, latitude: Long, longitude: Long)

Чтобы создать новый документ, я автоматически сопоставил Doc (выше) со структурой коллекции (в Play — вот так) и создал форму (для вставки/обновления коллекции) - все работает хорошо!

Но при редактировании документа; Я хотел бы обновить только некоторые поля (чтобы все поля не обновлялись). Поэтому я создал несколько case classes, чтобы разделить эти поля на более мелкие модели (например, DocPart1 и DocPart2), и сопоставил данные формы только с одним. Это побудило меня передать их в качестве параметра методу updateMultipleFields, как показано выше. Я надеюсь, что это имеет больше смысла.


person jesus g_force Harris    schedule 21.11.2017    source источник
comment
Не понятно, что вы хотите. У вас есть один объект и несколько разных наборов полей, которые обновляются в разных сценариях, и вы хотите сгруппировать каждый такой набор в явный тип? Или у вас есть какая-то коллекция, в которой по каким-то причинам хранятся объекты разных типов, которые должны обновляться по-разному? Или есть какой-то другой сценарий для такого кода?   -  person SergGr    schedule 22.11.2017
comment
@SerGr - спасибо за ответ. Пожалуйста, смотрите комментарии, и если вам нужны дополнительные разъяснения, дайте мне знать. АТБ   -  person jesus g_force Harris    schedule 22.11.2017
comment
Иисус, не могли бы вы также сказать, какую библиотеку вы используете для доступа к Mongo? Важно понимать, какой API он предоставляет для таких частичных обновлений.   -  person SergGr    schedule 22.11.2017
comment
@SerGr — я использую ReactiveMongo Play JSON как документировано здесь   -  person jesus g_force Harris    schedule 22.11.2017


Ответы (1)


Я не уверен, правильно ли я понимаю, что вам нужно. Тем не менее, вот какой-то код, который может быть им. Предположим, у нас есть класс FullDoc, определенный как:

case class FullDoc(_id: Option[BSONObjectID], name: String, other: String)

и у нас есть 2 частичных обновления, определенных как:

sealed trait BaseDocPart

case class DocPart1(name: String) extends BaseDocPart

case class DocPart2(other: String) extends BaseDocPart

Также предположим, что у нас есть доступ к нашей коллекции Mongo:

def docCollection: Future[JSONCollection] = ...

Итак, если я понимаю ваши требования, вам нужно что-то вроде этого:

def update[T <: BaseDocPart](oId: BSONObjectID, docPart: T)(implicit format: OFormat[T]) = {
  docCollection.flatMap(_.update(BSONDocument("_id" -> oId),
    JsObject(Seq("$set" -> Json.toJson(docPart)))))
}

По сути, основная хитрость заключается в использовании универсального T <: BaseDocPart и передаче implicit format: OFormat[T], чтобы мы могли преобразовать наш конкретный дочерний элемент BaseDocPart в JSON даже после стирания типа.

А вот дополнительный тестовый код (который я использовал в своем консольном приложении)

  implicit val fullFormat = Json.format[FullDoc]
  implicit val part1Format = Json.format[DocPart1]
  implicit val part2Format = Json.format[DocPart2]

  def insert(id: Int) = {
    val fullDoc = FullDoc(None, s"fullDoc_$id", s"other_$id")
    val insF: Future[WriteResult] = docCollection.flatMap(_.insert(fullDoc))
    val insRes = Await.result(insF, 2 seconds)
    println(s"insRes = $insRes")
  }

  def loadAndPrintAll() = {
    val readF = docCollection.flatMap(_.find(Json.obj()).cursor[FullDoc](ReadPreference.primaryPreferred).collect(100, Cursor.FailOnError[Vector[FullDoc]]()))
    val readRes = Await.result(readF, 2 seconds)
    println(s"readRes =\n${readRes.mkString("\n")}")
  }

  def loadRandomDocument(): FullDoc = {
    val readF = docCollection.flatMap(_.find(Json.obj()).cursor[FullDoc](ReadPreference.primaryPreferred).collect(100, Cursor.FailOnError[Vector[FullDoc]]()))
    val readRes = Await.result(readF, 2 seconds)
    readRes(Random.nextInt(readRes.length))
  }

  def updateWrapper[T <: BaseDocPart](oId: BSONObjectID, docPart: T)(implicit writer: OFormat[T]) = {
    val updateRes = Await.result(update(oId, docPart), 2 seconds)
    println(s"updateRes = $updateRes")
  }

  // pre-fill with some data    
  insert(1)
  insert(2)
  insert(3)
  insert(4)
  val newId: Int = ((System.currentTimeMillis() - 1511464148000L) / 100).toInt
  println(s"newId = $newId")

  val doc21: FullDoc = loadRandomDocument()
  println(s"doc21 = $doc21")
  updateWrapper(doc21._id.get, DocPart1(s"p1_modified_$newId"))

  val doc22: FullDoc = loadRandomDocument()
  println(s"doc22 = $doc22")
  updateWrapper(doc22._id.get, DocPart2(s"p2_modified_$newId"))

  loadAndPrintAll()
person SergGr    schedule 23.11.2017