Skip to content

Commit

Permalink
Add findAndUpdate and findAndRemove API
Browse files Browse the repository at this point in the history
  • Loading branch information
fehmicansaglam committed Jul 7, 2014
1 parent 1412139 commit 260cff2
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 2 deletions.
28 changes: 27 additions & 1 deletion bson/src/main/scala/dao/BsonDao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import reactivemongo.bson._
import reactivemongo.api.{ bulk, DB, QueryOpts }
import reactivemongo.api.indexes.Index
import reactivemongo.api.collections.default.BSONCollection
import reactivemongo.core.commands.{ LastError, GetLastError, Count }
import reactivemongo.core.commands.{ LastError, GetLastError, Count, FindAndModify, Update, Remove }
import reactivemongo.extensions.dsl.BsonDsl._
import play.api.libs.iteratee.{ Iteratee, Enumerator }
import Handlers._
Expand Down Expand Up @@ -127,6 +127,32 @@ abstract class BsonDao[Model, ID](db: () => DB, collectionName: String)(implicit
collection.find(selector).sort(sort).cursor[Model].collect[List]()
}

def findAndUpdate(
query: BSONDocument,
update: BSONDocument,
sort: BSONDocument = BSONDocument.empty,
fetchNewObject: Boolean = false,
upsert: Boolean = false): Future[Option[Model]] = {
val command = FindAndModify(
collection = collectionName,
query = query,
modify = Update(update, fetchNewObject),
upsert = upsert,
sort = if (sort == BSONDocument.empty) None else Some(sort))

collection.db.command(command).map(_.map(modelReader.read))
}

def findAndRemove(query: BSONDocument, sort: BSONDocument = BSONDocument.empty): Future[Option[Model]] = {
val command = FindAndModify(
collection = collectionName,
query = query,
modify = Remove,
sort = if (sort == BSONDocument.empty) None else Some(sort))

collection.db.command(command).map(_.map(modelReader.read))
}

def findRandom(selector: BSONDocument = BSONDocument.empty): Future[Option[Model]] = {
for {
count <- count(selector)
Expand Down
49 changes: 49 additions & 0 deletions bson/src/test/scala/dao/DummyBsonDaoSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import reactivemongo.extensions.dsl.BsonDsl._
import reactivemongo.bson.Macros.Options.Verbose
import reactivemongo.extensions.model.DummyModel
import reactivemongo.extensions.util.Logger
import reactivemongo.extensions.Implicits._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

Expand Down Expand Up @@ -57,6 +58,54 @@ class DummyBsonDaoSpec
}
}

it should "findAndUpdate one document and retreive the old document" in {
val dummyModel = DummyModel(name = "foo", surname = "bar", age = 32)

val futureResult = for {
insertResult <- dao.insert(dummyModel)
oldDocument <- ~dao.findAndUpdate("age" $eq dummyModel.age, $inc("age" -> 32))
newDocument <- ~dao.findOne("age" $eq 64)
} yield (oldDocument, newDocument)

whenReady(futureResult) {
case (oldDocument, newDocument) =>
oldDocument._id shouldBe dummyModel._id
oldDocument.age shouldBe dummyModel.age
newDocument.age shouldBe 64
}
}

it should "findAndUpdate one document and retreive the new document" in {
val dummyModel = DummyModel(name = "foo", surname = "bar", age = 32)

val futureResult = for {
insertResult <- dao.insert(dummyModel)
newDocument <- ~dao.findAndUpdate("age" $eq dummyModel.age, $inc("age" -> 32), fetchNewObject = true)
} yield newDocument

whenReady(futureResult) { newDocument =>
newDocument._id shouldBe dummyModel._id
newDocument.age shouldBe 64
}
}

it should "findAndRemove one document" in {
val dummyModel = DummyModel(name = "foo", surname = "bar", age = 32)

val futureResult = for {
insertResult <- dao.insert(dummyModel)
oldDocument <- ~dao.findAndRemove("age" $eq dummyModel.age)
afterCount <- dao.count()
} yield (oldDocument, afterCount)

whenReady(futureResult) {
case (oldDocument, afterCount) =>
oldDocument._id shouldBe dummyModel._id
oldDocument.age shouldBe dummyModel.age
afterCount shouldBe 0
}
}

it should "find one random document in selected documents" in {
val dummyModels = DummyModel.random(100)

Expand Down
26 changes: 26 additions & 0 deletions core/src/main/scala/dao/Dao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,32 @@ abstract class Dao[C <: Collection: CollectionProducer, Structure, Model, ID, Wr
*/
def findAll(selector: Structure, sort: Structure): Future[List[Model]]

/**
* Updates and returns a single model. It returns the old document by default.
*
* @param query The selection criteria for the update.
* @param update Performs an update of the selected model.
* @param sort Determines which model the operation updates if the query selects multiple models.
* findAndUpdate() updates the first model in the sort order specified by this argument.
* @param fetchNewObject When true, returns the updated model rather than the original.
* @param upsert When true, findAndUpdate() creates a new model if no model matches the query.
*/
def findAndUpdate(
query: Structure,
update: Structure,
sort: Structure,
fetchNewObject: Boolean,
upsert: Boolean): Future[Option[Model]]

/**
* Removes and returns a single model.
*
* @param query The selection criteria for the remove.
* @param sort Determines which model the operation removes if the query selects multiple models.
* findAndRemove() removes the first model in the sort order specified by this argument.
*/
def findAndRemove(query: Structure, sort: Structure): Future[Option[Model]]

/** Retrieves the model with the given `id`. */
def findById(id: ID): Future[Option[Model]]

Expand Down
38 changes: 37 additions & 1 deletion json/src/main/scala/dao/JsonDao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import scala.util.Random
import scala.concurrent.{ Future, Await }
import scala.concurrent.duration._
import play.api.libs.json.{ Json, JsObject, Format, Writes }
import reactivemongo.bson.BSONDocument
import reactivemongo.api.{ bulk, DB, QueryOpts }
import reactivemongo.api.indexes.Index
import reactivemongo.core.commands.{ LastError, GetLastError, Count }
import reactivemongo.core.commands.{ LastError, GetLastError, Count, FindAndModify, Update, Remove }
import play.modules.reactivemongo.json.collection.JSONCollection
import play.modules.reactivemongo.json.ImplicitBSONHandlers.JsObjectWriter
import reactivemongo.extensions.BSONFormats
import reactivemongo.extensions.dao.{ Dao, LifeCycle, ReflexiveLifeCycle }
import reactivemongo.extensions.json.dsl.JsonDsl._
import play.api.libs.iteratee.{ Iteratee, Enumerator }
Expand Down Expand Up @@ -80,6 +82,14 @@ import play.api.libs.iteratee.{ Iteratee, Enumerator }
abstract class JsonDao[Model: Format, ID: Writes](db: () => DB, collectionName: String)(implicit lifeCycle: LifeCycle[Model, ID] = new ReflexiveLifeCycle[Model, ID])
extends Dao[JSONCollection, JsObject, Model, ID, Writes](db, collectionName) {

private def toBSONDocument(document: JsObject): BSONDocument = {
BSONFormats.BSONDocumentFormat.reads(document).get
}

private def toJsObject(document: BSONDocument): JsObject = {
BSONFormats.BSONDocumentFormat.writes(document).as[JsObject]
}

def ensureIndexes(): Future[Traversable[Boolean]] = Future sequence {
autoIndexes map { index =>
collection.indexesManager.ensure(index)
Expand Down Expand Up @@ -125,6 +135,32 @@ abstract class JsonDao[Model: Format, ID: Writes](db: () => DB, collectionName:
collection.find(selector).sort(sort).cursor[Model].collect[List]()
}

def findAndUpdate(
query: JsObject,
update: JsObject,
sort: JsObject = Json.obj(),
fetchNewObject: Boolean = false,
upsert: Boolean = false): Future[Option[Model]] = {
val command = FindAndModify(
collection = collectionName,
query = toBSONDocument(query),
modify = Update(toBSONDocument(update), fetchNewObject),
upsert = upsert,
sort = if (sort == Json.obj()) None else Some(toBSONDocument(sort)))

collection.db.command(command).map(_.map(bson => implicitly[Format[Model]].reads(toJsObject(bson)).get))
}

def findAndRemove(query: JsObject, sort: JsObject = Json.obj()): Future[Option[Model]] = {
val command = FindAndModify(
collection = collectionName,
query = toBSONDocument(query),
modify = Remove,
sort = if (sort == Json.obj()) None else Some(toBSONDocument(sort)))

collection.db.command(command).map(_.map(bson => implicitly[Format[Model]].reads(toJsObject(bson)).get))
}

def findRandom(selector: JsObject = Json.obj()): Future[Option[Model]] = {
for {
count <- count(selector)
Expand Down
49 changes: 49 additions & 0 deletions json/src/test/scala/dao/DummyJsonDaoSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import play.modules.reactivemongo.json.BSONFormats._
import reactivemongo.extensions.json.model.DummyModel
import reactivemongo.extensions.json.dsl.JsonDsl._
import reactivemongo.extensions.util.Logger
import reactivemongo.extensions.Implicits._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

Expand Down Expand Up @@ -57,6 +58,54 @@ class DummyJsonDaoSpec
}
}

it should "findAndUpdate one document and retreive the old document" in {
val dummyModel = DummyModel(name = "foo", surname = "bar", age = 32)

val futureResult = for {
insertResult <- dao.insert(dummyModel)
oldDocument <- ~dao.findAndUpdate("age" $eq dummyModel.age, $inc("age" -> 32))
newDocument <- ~dao.findOne("age" $eq 64)
} yield (oldDocument, newDocument)

whenReady(futureResult) {
case (oldDocument, newDocument) =>
oldDocument._id shouldBe dummyModel._id
oldDocument.age shouldBe dummyModel.age
newDocument.age shouldBe 64
}
}

it should "findAndUpdate one document and retreive the new document" in {
val dummyModel = DummyModel(name = "foo", surname = "bar", age = 32)

val futureResult = for {
insertResult <- dao.insert(dummyModel)
newDocument <- ~dao.findAndUpdate("age" $eq dummyModel.age, $inc("age" -> 32), fetchNewObject = true)
} yield newDocument

whenReady(futureResult) { newDocument =>
newDocument._id shouldBe dummyModel._id
newDocument.age shouldBe 64
}
}

it should "findAndRemove one document" in {
val dummyModel = DummyModel(name = "foo", surname = "bar", age = 32)

val futureResult = for {
insertResult <- dao.insert(dummyModel)
oldDocument <- ~dao.findAndRemove("age" $eq dummyModel.age)
afterCount <- dao.count()
} yield (oldDocument, afterCount)

whenReady(futureResult) {
case (oldDocument, afterCount) =>
oldDocument._id shouldBe dummyModel._id
oldDocument.age shouldBe dummyModel.age
afterCount shouldBe 0
}
}

it should "find one random document in selected documents" in {
val dummyModels = DummyModel.random(100)

Expand Down

0 comments on commit 260cff2

Please sign in to comment.