Игра: Как реализовать вермонго с ReactiveMongo

Я хочу обновить документ JSON в MongoDB следующим образом:

{
  "_id":{"$oid":"52dfc13ec20900c2093155cf"},
  "email": "[email protected]",
  "name": "joe",
  "_version": 2
}

... и хотите создать такой документ vermongo при каждом обновлении:

{
   "_id { "_id":{"$oid":"52dfc13ec20900c2093155cf"}, "_version": 1},
   "email": "[email protected]",
   "name": "joe",
   "_version": 1,
   "_timestamp" : "2014-02-02T00:11:45.542"
}

Я пробовал такое решение:

trait MyDao {

  ...

  private val shadowCollection = ReactiveMongoPlugin.db.collection[JSONCollection](
    collection.name + ".vermongo"
  )

  private def toVersioned(deleted: Boolean) = __.json.update(
    (__ \ '_id).json.copyFrom((__ \ '_id \ '$oid).json.pickBranch) andThen
    (__ \ '_id \ '_version).json.copyFrom((__ \ '_version).json.pick) andThen
 // (__ \ '_version).json.put(if (deleted) JsString(s"deleted:$version") else JsNumber(version)) andThen
    (__ \ '_timestamp).json.put(Json.toJson(LocalDateTime.now))
  ) 

  private def version(doc: JsValue, deleted: Boolean): Future[LastError] = {
    shadowCollection.insert(doc.transform(toVersioned(deleted)).get)
  }
}

Метод toVersioned имеет три проблемы:

Строка 1: мультиполя не создаются _id

Строка 2: происходит сбой, когда я пытаюсь создать _version в качестве второго поля _id

Строка 3: (закомментировано), если параметр deleted равен true, я хочу пометить документ как удаленный, заменив "_version": 1 на "_version": "deleted:1"; мне не ясно, как справиться с условием здесь.


person j3d    schedule 01.02.2014    source источник


Ответы (1)


Фантастический вопрос; Я давно собирался узнать о преобразователях JSON в Play и, наконец, отличная ситуация, чтобы попробовать их.

Я должен отметить, что мне совершенно не удавалось заставить это работать правильно, пока я не добавил очень важный импорт functional.syntax._:

import play.api.libs.functional.syntax._

Решение началось с примера трансформера Gizmo -> Gremlin на странице трансформеров; вот где я получил использование and и reduce, что полностью меняет (извините за каламбур) то, как все это работает.

Итак, вот оно:

private def toVersioned(deleted: Boolean) = (__.json.update(
  ( __ \ '_id ).json.copyFrom((__ \ '_id \ '$oid).json.pickBranch) and
  ( __ \ '_id \ '_version).json.copyFrom((__ \ '_version).json.pick) and
    (__ \ '_version).json.update(
      of[JsValue].map { case JsNumber(oldVersion) =>
        if (deleted) JsString(s"deleted:$oldVersion") else JsNumber(oldVersion)
      }
    ) and
    (__ \ '_timestamp).json.put(Json.toJson(LocalDateTime.now)) reduce
  )
  andThen
  ( __ \ '_id \ '$oid ).json.prune
)

Ключевые моменты:

  • Мне не удалось (за разумное время) добиться «перемещения» узла _id за одно преобразование, поэтому первый проход копирует его «вниз» на один уровень, а второй проход (после andThen) удаляет старый $oid. Почти наверняка есть способ...

  • Использование and, по-видимому, меняет область действия copyFrom, позволяя правильно выбирать _version из верхнего уровня - при использовании andThen казалось, что это работает только при спуске в узлы «ниже» цели.

  • Я вполне доволен обработкой deleted здесь - map кажется идиоматичным как для Scala, так и для PlayJSON. Of[JsValue] необходим, потому что здесь мы возвращаем либо JsString, либо JsNumber, а JsValue кажется подходящим суперклассом.

person millhouse    schedule 02.02.2014
comment
... фантастический ответ ;-) Он работает и делает именно то, что мне нужно. Мне просто нужно было устроить так, чтобы удаление предупреждения не могло быть исчерпывающим. Большое спасибо! - person j3d; 02.02.2014