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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With