Play: How to implement vermongo with ReactiveMongo
json, mongodb, playframework, reactivemongo, scala
Solution
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
Problem
I want to update a JSON document in MongoDB like this: ``` { "_id":{"$oid":"52dfc13ec20900c2093155cf"}, "email": "joe@domain.com", "name": "joe", "_version": 2 } ``` ... and want to create a vermongo document like this on each update: ``` { "_id { "_id":{"$oid":"52dfc13ec20900c2093155cf"}, "_version": 1}, "email": "joe@domain.com", "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.