Permalink
Browse files

[reactivemongo] Convenient response maker for write operation. More s…

…pecs for Driver/AcolyteDSL, update documentation.
  • Loading branch information...
cchantep
cchantep committed Sep 18, 2014
1 parent 14b59bc commit f925d4ffcd64388836d6da252b3c13b3bff32aac
View
@@ -16,13 +16,101 @@ Then any connection created will be managed by your Acolyte (query & writer) han
```scala
import reactivemongo.api.MongoDriver
-import acolyte.reactivemongo.AcolyteDSL.{ driver, handle }
+import acolyte.reactivemongo.AcolyteDSL.driver
val mongoDriver: MongoDriver = driver {
- ??? // dispatch query and write request as you want using pattern matching
+ yourConnectionHandler
+ // ... dispatch query and write request as you want using pattern matching
}
+```
+
+### Setup driver behaviour
+
+Driver behaviour is configured using a connection handler, itself based on query and write handler, managing respectively Mongo queries or write operations, and returning appropriate result.
+
+You can start looking at empty/no-op connection handler. This one has no query or write handler, so has no response can be provided to any command performed with ReactiveMongo configured in this way, it will raise explicit error `No response: ...` for every request.
+
+```scala
+import acolyte.reactivemongo.AcolyteDSL
+
+val noOpDriver = AcolyteDSL driver {
+ AcolyteDSL handle/* ConnectionHandler.empty */
+}
+
+// Then when Mongo code is given this driver instead of production one ...
+// (see DI or cake pattern)
+import scala.util.Failure
+import reactivemongo.api.MongoConnection
+import reactivemongo.bson.BSONDocument
+
+// driver = noOpDriver
+val connect: MongoConnection = driver.connection(List(anyOptions))
+val db = connect("anyDbName")
+val col = db("anyColName")
+
+col.find(BSONDocument("anyQuery" -> 1).cursor[BSONDocument].toList().
+ onComplete {
+ case Failure(err) => ???
+ // Will be there with "No response: " error as nothing is configured
+ }
+
+col.insert(BSONDocument("prop" -> "value")).onComplete {
+ case Failure(err) => ???
+ // Will be there with "No response: " error as nothing is configured
+}
+```
+
+Then we can really play handlers. To handle Mongo query and to return the kind of result your code should work with, you can do as following.
+
+```scala
+import acolyte.reactivemongo.{ AcolyteDSL, Request }
-val noOpDriver = driver { handle/* ConnectionHandler.empty */}
+val readOnlyDriver = AcolyteDSL driver {
+ AcolyteDSL handleQuery { req: Request => aResponse }
+}
+
+// Then when Mongo code is given this driver instead of production one ...
+// (see DI or cake pattern)
+
+col.find(BSONDocument("anyQuery" -> 1).cursor[BSONDocument].toList().
+ onComplete {
+ case Success(res) => ??? // In case of response given by provided handler
+ case Failure(err) => ??? // "No response: " if case not handled
+ }
+```
+
+In the same way, write operations can be responded with appropriate result.
+
+```scala
+import acolyte.reactivemongo.{ AcolyteDSL, Request, WriteOp }
+
+val writeOnlyDriver = AcolyteDSL driver {
+ AcolyteDSL handleWrite { (op: WriteOp, req: Request) => aResponse }
+}
+
+// Then when Mongo code is given this driver instead of production one ...
+// (see DI or cake pattern)
+
+col.insert(BSONDocument("prop" -> "value")).onComplete {
+ case Success(res) => ??? // In case or response given by provided handler
+ case Failure(err) => ??? // "No response: " if case not handled
+}
+```
+
+Obviously connection handler can manage both query and write:
+
+```
+import acolyte.reactivemongo.{ AcolyteDSL, Request, WriteOp }
+
+val completeHandler = AcolyteDSL driver {
+ AcolyteDSL handleQuery { req: Request =>
+ // First define query handling
+ aQueryResponse
+ } withWriteHandler { (op: WriteOp, req: Request) =>
+ // Then define write handling
+ aWriteResponse
+ }
+}
```
### Request patterns
@@ -17,13 +17,45 @@ object AcolyteDSL {
new MongoDriver(Some(Akka.actorSystem(handler)))
/**
- * Creates an empty handler.
+ * Creates an empty connection handler.
*
* {{{
- * import acolyte.reactivemongo.AcolyteDSL.{ connection, handleStatement }
+ * import acolyte.reactivemongo.AcolyteDSL.{ driver, handle }
*
- * connection { handleStatement }
+ * driver(handle)
* }}}
*/
def handle: ConnectionHandler = ConnectionHandler.empty
+
+ /**
+ * Creates a connection handler with given query handler,
+ * but no write handler.
+ *
+ * {{{
+ * import acolyte.reactivemongo.AcolyteDSL.{ driver, handleQuery }
+ * import acolyte.reactivemongo.Request
+ *
+ * driver(handleQuery { req: Request => aResponse })
+ * }}}
+ *
+ * @see [[ConnectionHandler.withWriteHandler]]
+ */
+ def handleQuery(handler: QueryHandler): ConnectionHandler =
+ ConnectionHandler(handler)
+
+ /**
+ * Creates a connection handler with given write handler,
+ * but no query handler.
+ *
+ * {{{
+ * import acolyte.reactivemongo.AcolyteDSL.{ driver, handleWrite }
+ * import acolyte.reactivemongo.{ Request, WriteOp }
+ *
+ * driver(handleWrite { (op: WriteOp, req: Request) => aResponse })
+ * }}}
+ *
+ * @see [[ConnectionHandler.withQueryHandler]]
+ */
+ def handleWrite(handler: WriteHandler): ConnectionHandler =
+ ConnectionHandler(writeHandler = handler)
}
@@ -128,10 +128,8 @@ object WriteHandler {
* {{{
* import acolyte.reactivemongo.{ Request, WriteHandler, WriteOp }
*
- * val handler1: WriteHandler = // Returns a successful empty response
- * (w: (WriteOp, Request)) => WriteResponse(false)
- *
- * val handler2 = WriteHandler { (op: WriteOp,
+ * val handler: WriteHandler = // Returns a successful for 1 doc
+ * (w: (WriteOp, Request)) => WriteResponse(1, false)
* }}}
*/
implicit def apply(f: (WriteOp, Request) PreparedResponse): WriteHandler = new WriteHandler {
@@ -30,6 +30,19 @@ object WriteResponseMaker {
def apply(channelId: Int, up: (Int, Boolean)): Option[Try[Response]] = Some(MongoDB.WriteSuccess(channelId, up._1, up._2))
}
+ /**
+ * {{{
+ * import acolyte.reactivemongo.WriteResponseMaker
+ *
+ * val maker = implicitly[WriteResponseMaker[Int]]
+ * }}}
+ */
+ implicit def SuccessNotUpdatedWriteResponseMaker =
+ new WriteResponseMaker[Int] {
+ def apply(channelId: Int, count: Int): Option[Try[Response]] =
+ Some(MongoDB.WriteSuccess(channelId, count))
+ }
+
/**
* {{{
* import acolyte.reactivemongo.WriteResponseMaker
@@ -4,6 +4,8 @@ import scala.util.Try
import scala.concurrent.{ Await, Future }
import scala.concurrent.duration.Duration
+import resource.{ ManagedResource, managed }
+
import reactivemongo.api.{ MongoDriver, MongoConnection, DefaultDB }
import reactivemongo.api.collections.default.BSONCollection
import reactivemongo.bson.{
@@ -40,14 +42,14 @@ object DriverSpec extends org.specs2.mutable.Specification
"return expected query result" >> {
"when is successful #1" in withCol(query1.collection) { col
- await(col.find(query1.body).cursor[BSONDocument].toList()).
+ awaitRes(col.find(query1.body).cursor[BSONDocument].toList()).
aka("query result") must beSuccessfulTry[List[BSONDocument]].like {
case ValueDocument(("b", BSONInteger(3)) :: Nil) :: Nil ok
}
}
"when is successful #2" in withCol(query2.collection) { col
- await(col.find(query2.body).cursor[BSONDocument].toList()).
+ awaitRes(col.find(query2.body).cursor[BSONDocument].toList()).
aka("query result") must beSuccessfulTry[List[BSONDocument]].like {
case ValueDocument(("d", BSONDouble(4.56d)) :: Nil) ::
ValueDocument(("ef", BSONString("ghi")) :: Nil) :: Nil ok
@@ -56,25 +58,43 @@ object DriverSpec extends org.specs2.mutable.Specification
"as error when query handler returns no query result" in withCol(
query3.collection) { col
- await(col.find(query3.body).cursor[BSONDocument].toList()).
+ awaitRes(col.find(query3.body).cursor[BSONDocument].toList()).
aka("query result") must beFailedTry.
withThrowable[DetailedDatabaseException](".*No response: .*")
}
- "as error when connection handler is empty" in {
- false must beTrue
- }
+ "as error when connection handler is empty" in withCol(query3.collection,
+ collection(_, db("test-db",
+ connect(managed(AcolyteDSL driver AcolyteDSL.handle))))) { col
+
+ awaitRes(col.find(query3.body).cursor[BSONDocument].toList()).
+ aka("query result") must beFailedTry.
+ withThrowable[DetailedDatabaseException](".*No response: .*")
+ }
+
+ "as error when query handler is undefined" in withCol(query3.collection,
+ collection(_, db("test-db",
+ connect(managed(AcolyteDSL driver AcolyteDSL.handleWrite(
+ { (_: WriteOp, _: Request) WriteResponse(1 /* one doc */ ) }
+ )))))) { col
+
+ awaitRes(col.find(query3.body).cursor[BSONDocument].toList()).
+ aka("query result") must beFailedTry.
+ withThrowable[DetailedDatabaseException](".*No response: .*")
+
+ }
}
"return expected write result" >> {
"when error is raised without code" in withCol(write1._2.collection) {
col
- await(col.remove(write1._2.body)) aka "write result" must beFailedTry.
+ awaitRes(col.remove(write1._2.body)).
+ aka("write result") must beFailedTry.
withThrowable[LastError](".*Error #2.*code = -1.*")
}
"when successful" in withCol(write2._2.collection) { col
- await(col.insert(write2._2.body)).
+ awaitRes(col.insert(write2._2.body)).
aka("result") must beSuccessfulTry.like {
case lastError
lastError.elements.toList aka "body" must beLike {
@@ -89,18 +109,39 @@ object DriverSpec extends org.specs2.mutable.Specification
}
}
- "when no write result" in withCol(write3._2.collection) { col
- await(col.update(BSONDocument("name" -> "x"), write3._2.body)).
- aka("result") must beFailedTry.withThrowable[LastError](
- ".*No response: .*")
- }
+ "as error when write handler returns no write result" in withCol(
+ write3._2.collection) { col
+ awaitRes(col.update(BSONDocument("name" -> "x"), write3._2.body)).
+ aka("result") must beFailedTry.withThrowable[LastError](
+ ".*No response: .*")
+ }
+
+ "as error when connection handler is empty" in withCol(query3.collection,
+ collection(_, db("test-db",
+ connect(managed(AcolyteDSL driver AcolyteDSL.handle))))) { col
+
+ awaitRes(col.update(BSONDocument("name" -> "x"), write3._2.body)).
+ aka("result") must beFailedTry.withThrowable[LastError](
+ ".*No response: .*")
+ }
+
+ "as error when write handler is undefined" in withCol(query3.collection,
+ collection(_, db("test-db",
+ connect(managed(AcolyteDSL driver AcolyteDSL.handleQuery(
+ { _: Request
+ QueryResponse(BSONDocument("prop" -> "A"))
+ })))))) { col
+
+ awaitRes(col.update(BSONDocument("name" -> "x"), write3._2.body)).
+ aka("result") must beFailedTry.withThrowable[LastError](
+ ".*No response: .*")
+
+ }
}
}
// ---
- import resource.{ ManagedResource, managed }
-
val driver: ManagedResource[MongoDriver] =
managed(AcolyteDSL driver chandler1)
@@ -112,5 +153,5 @@ object DriverSpec extends org.specs2.mutable.Specification
def withCol[T](n: String, col: String ManagedResource[BSONCollection] = collection(_))(f: BSONCollection T): T = col(n).acquireAndGet(f)
- def await[T](f: Future[T], tmout: Duration = Duration(5, "seconds")): Try[T] = Try[T](Await.result(f, tmout))
+ def awaitRes[T](f: Future[T], tmout: Duration = Duration(5, "seconds")): Try[T] = Try[T](Await.result(f, tmout))
}
@@ -43,7 +43,7 @@ object WriteResponseSpec
}
"be made for successful result" >> {
- "with a boolean updatedExisting flag" in {
+ "with count and updatedExisting flag" in {
WriteResponse(1 -> true) aka "prepared" must beLike {
case prepared prepared(3) aka "applied" must beSome.which(
_ aka "result" must beResponse {
@@ -52,7 +52,7 @@ object WriteResponseSpec
}
}
- "with a boolean updatedExisting flag using named factory" in {
+ "with count and updatedExisting using named factory" in {
WriteResponse.successful(0, false) aka "prepared" must beLike {
case prepared prepared(3) aka "applied" must beSome.which(
_ aka "result" must beResponse {
@@ -61,6 +61,24 @@ object WriteResponseSpec
}
}
+ "with count using generic factory" in {
+ WriteResponse(1) aka "prepared" must beLike {
+ case prepared prepared(3) aka "applied" must beSome.which(
+ _ aka "result" must beResponse {
+ _ aka "response" must beWriteSuccess(1, false)
+ })
+ }
+ }
+
+ "with count using named factory" in {
+ WriteResponse.successful(2) aka "prepared" must beLike {
+ case prepared prepared(3) aka "applied" must beSome.which(
+ _ aka "result" must beResponse {
+ _ aka "response" must beWriteSuccess(2, false)
+ })
+ }
+ }
+
"with a unit (effect)" in {
WriteResponse() aka "prepared" must beLike {
case prepared prepared(4) aka "applied" must beSome.which(

0 comments on commit f925d4f

Please sign in to comment.