Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play: How to implement vermongo with ReactiveMongo

I want to update a JSON document in MongoDB like this:

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

... and want to create a vermongo document like this on each update:

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

I've tried a solution like this:

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

The toVersioned method has three problems:

Line 1: It doesn't create the multi-fields _id

Line 2: It crashes when I try to create _version as the second field of _id

Line 3: (commented out) if parameter deleted is true, I'd like to mark the document as deleted by replacing "_version": 1 with "_version": "deleted:1"; it is not clear to me how to handle the condition here.

like image 465
j3d Avatar asked Oct 20 '22 15:10

j3d


1 Answers

Fantastic question; I'd been meaning to learn about Play's JSON transformers for ages, and finally a great situation to try them out.

I should note that I was completely unable to get this to work correctly until I added the all-important functional.syntax._ import:

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

The solution started out from the Gizmo -> Gremlin transformer example on the transformers page; that's where I got the usage of the and and reduce, which completely transforms (pardon the pun) how the whole thing works.

So here it is:

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
)

Key points:

  • I couldn't (within a reasonable amount of time) achieve a "move" of the _id node in the one transform, hence the first pass copies it "down" one level, and the second pass (after the andThen) deletes the old $oid. There's almost-certainly a way...

  • The use of and seems to change the scope of the copyFrom, allowing the _version to be picked out of the top-level correctly - when using andThen it seemed to only work if descending into nodes "below" the target

  • I'm quite happy with the handling of deleted here - the map seems both Scala- and PlayJSON-idiomatic. The Of[JsValue] is necessary because we return either a JsString or a JsNumber here, and JsValue seemed like a suitable superclass

like image 172
millhouse Avatar answered Oct 23 '22 12:10

millhouse