Skip to content

Commit

Permalink
Natural conversions for the ReactiveMongo api (#87)
Browse files Browse the repository at this point in the history
* Refactor withDriver to use natural conversion between async and pure result
* Refactor withFlatConnection with natural conversion
* Refactor withFlatDB using natural conversion
* Refactor withFlatCollection using natural conversion
* Refactor withFlat{Write,Query}Handler and withFlat{Write,Query}Result using natural conversion
* Eliminate duplication in WithHandler using better composition
* Refactor WithCollection to eliminate duplication by composing with withDB
* Finalize WithDB refactoring
  • Loading branch information
cchantep committed Jul 22, 2018
1 parent b861ff1 commit a8cc52c
Show file tree
Hide file tree
Showing 13 changed files with 441 additions and 396 deletions.
2 changes: 1 addition & 1 deletion project/Dependencies.scala
Expand Up @@ -4,6 +4,6 @@ import Keys._
object Dependencies {
val specsVer = Def.setting[String] {
if (scalaVersion.value startsWith "2.10") "3.9.5" // 4.0.1 not avail
else "4.2.0"
else "4.3.2"
}
}
12 changes: 3 additions & 9 deletions reactive-mongo/src/main/scala/Akka.scala
Expand Up @@ -93,15 +93,9 @@ private[reactivemongo] class Actor(handler: ConnectionHandler)
val Key = k + "s"

es.collectFirst {
case (Key, a @ BSONArray(_))
a.values.headOption match {
case Some(ValueDocument(("q", sel @ BSONDocument(_)) ::
("u", fil @ BSONDocument(_)) :: _)) List(sel, fil)

case Some(ValueDocument(
("q", sel @ BSONDocument(_)) :: _)) List(sel)

}
case (Key, a @ BSONArray(_)) a.values.toList.collect {
case doc @ BSONDocument(_) doc
}
}
}

Expand Down
43 changes: 43 additions & 0 deletions reactive-mongo/src/main/scala/ComposeWithCompletion.scala
@@ -0,0 +1,43 @@
package acolyte.reactivemongo

import scala.concurrent.{ ExecutionContext, Future }

sealed trait ComposeWithCompletion[Out] {
type Outer <: Future[_]

def apply[In](input: Future[In], f: In Out)(onComplete: In Unit)(implicit ec: ExecutionContext): Outer
}

object ComposeWithCompletion extends LowPriorityCompose {
type Id[T] = ComposeWithCompletion[Future[T]] { type Outer = Future[T] }

implicit def futureOut[T]: Id[T] = new ComposeWithCompletion[Future[T]] {
type Outer = Future[T]

def apply[In](input: Future[In], f: In Future[T])(onComplete: In Unit)(implicit ec: ExecutionContext): Future[T] = input.flatMap { in
f(in).andThen {
case _ onComplete(in)
}
}

override val toString = "futureOut"
}
}

sealed trait LowPriorityCompose { _: ComposeWithCompletion.type
type Map[T] = ComposeWithCompletion[T] { type Outer = Future[T] }

implicit def pureOut[T]: Map[T] = new ComposeWithCompletion[T] {
type Outer = Future[T]

def apply[In](input: Future[In], f: In T)(onComplete: In Unit)(implicit ec: ExecutionContext): Future[T] = input.map { in
try {
f(in)
} finally {
onComplete(in)
}
}

override val toString = "pureOut"
}
}
2 changes: 2 additions & 0 deletions reactive-mongo/src/main/scala/DriverManager.scala
Expand Up @@ -40,6 +40,8 @@ object DriverManager {
def open() = existing

def releaseIfNecessary(driver: MongoDriver): Boolean = false

override lazy val toString = "NoOpDriverManager"
}
}

Expand Down
63 changes: 52 additions & 11 deletions reactive-mongo/src/main/scala/Request.scala
Expand Up @@ -144,23 +144,55 @@ object InsertRequest {
Some(insert._2.collection (body.elements.map {
case BSONElement(name, value) name value
}.toList))

case _ None
}
}

/** Update request */
object UpdateElement {
/**
* Extracts an update element: query (q), update operator (u),
* and option `upsert` and `multi`.
*
* Matches value of the following form:
*
* {{{
* { "q": { .. }, "u": { .. }, "upsert": true, "multi": false }
* }}}
*
* @return (q, u, upsert, multi)
*/
def unapply(value: BSONValue): Option[(BSONDocument, BSONDocument, Boolean, Boolean)] = value match {
case el @ BSONDocument(_) for {
q el.getAs[BSONDocument]("q")
u el.getAs[BSONDocument]("u")
upsert = el.getAs[Boolean]("upsert").getOrElse(false)
multi = el.getAs[Boolean]("multi").getOrElse(false)
} yield (q, u, upsert, multi)

case _ None
}
}

/** Update request */
object UpdateRequest {
/** @return Collection name, elements of selector/document to be updated. */
def unapply(update: (WriteOp, Request)): Option[(String, List[(String, BSONValue)], List[(String, BSONValue)])] = (update._1, update._2.body) match {
case (UpdateOp, selector :: body :: Nil)
Some((
update._2.collection,
selector.elements.map {
case BSONElement(name, value) name value
}.toList,
body.elements.map {
case BSONElement(name, value) name value
}.toList))
def unapply(update: (WriteOp, Request)): Option[(String, List[(String, BSONValue)], List[(String, BSONValue)], Boolean, Boolean)] = (update._1, update._2.body) match {
case (UpdateOp, first :: _) UpdateElement.unapply(first).collect {
case (selector, body, upsert, multi)
(
update._2.collection,
selector.elements.map {
case BSONElement(name, value) name value
}.toList,
body.elements.map {
case BSONElement(name, value) name value
}.toList,
upsert,
multi)
}

case _ None
}
}
Expand All @@ -169,10 +201,19 @@ object UpdateRequest {
object DeleteRequest {
/** @return Collection name and elements of selector. */
def unapply(delete: (WriteOp, Request)): Option[(String, List[(String, BSONValue)])] = (delete._1, delete._2.body) match {
case (DeleteOp, selector :: Nil)
case (DeleteOp, ValueDocument(
("q", selector @ BSONDocument(_)) :: _) :: Nil)
Some(delete._2.collection (selector.elements.map {
case BSONElement(name, value) name value
}.toList))

case (DeleteOp, ValueDocument(
("q", selector @ BSONDocument(_)) :: _) ::
ValueDocument(_ /*options*/ ) :: _)
Some(delete._2.collection (selector.elements.map {
case BSONElement(name, value) name value
}.toList))

case _ None
}
}
Expand Down
115 changes: 32 additions & 83 deletions reactive-mongo/src/main/scala/WithCollection.scala
@@ -1,20 +1,25 @@
package acolyte.reactivemongo

import scala.concurrent.{ ExecutionContext, Future }
import scala.concurrent.ExecutionContext
import reactivemongo.api.collections.bson.BSONCollection
import reactivemongo.api.{ DB, MongoConnection, MongoDriver }

/** Functions to work with a Mongo collection (provided DB functions). */
trait WithCollection { withDB: WithDB
/**
* Functions to work with a Mongo collection (provided DB functions).
*
* @define conParam Connection manager parameter (see [[ConnectionManager]])
* @define nameParam the name of the collection
*/
trait WithCollection { self: WithDB
/**
* Works with specified collection from MongoDB "acolyte"
* resolved using given driver initialized with Acolyte for ReactiveMongo
* (should not be used with other driver instances).
* Driver and associated resources are released
* after the function `f` the result `Future` is completed.
*
* @param conParam Connection manager parameter (see [[ConnectionManager]])
* @param name Collection name
* @param conParam $conParam
* @param name $nameParam
* @param f Function applied to resolved Mongo collection
*
* {{{
Expand All @@ -27,16 +32,22 @@ trait WithCollection { withDB: WithDB ⇒
* "Result"
* }
* }}}
* @see AcolyteDSL.withFlatDB[A,B]
* @see AcolyteDSL.withDB[A,B]
*/
def withCollection[A, B](conParam: A, name: String)(f: BSONCollection B)(implicit d: MongoDriver, m: ConnectionManager[A], c: ExecutionContext): Future[B] = withFlatDB(conParam) { db Future(f(db(name))) }
def withCollection[A, B](conParam: A, name: String)(f: BSONCollection B)(
implicit
d: MongoDriver,
m: ConnectionManager[A],
ec: ExecutionContext,
compose: ComposeWithCompletion[B]): compose.Outer =
withDB(conParam) { db f(db.collection(name)) }

/**
* Works with specified collection from MongoDB "acolyte"
* resolved using given connection.
*
* @param con Previously initialized connection
* @param name Collection name
* @param name $nameParam
* @param f Function applied to resolved Mongo collection
*
* {{{
Expand All @@ -50,100 +61,38 @@ trait WithCollection { withDB: WithDB ⇒
* }
* }
* }}}
* @see WithDriver.withFlatDB[T]
* @see WithDriver.withDB[T]
*/
def withCollection[T](con: MongoConnection, name: String)(f: BSONCollection T)(implicit c: ExecutionContext): Future[T] = withFlatDB(con) { db Future(f(db(name))) }
def withCollection[T](con: MongoConnection, name: String)(
f: BSONCollection T)(
implicit
ec: ExecutionContext,
compose: ComposeWithCompletion[T]): compose.Outer =
withDB(con) { db f(db.collection(name)) }

/**
* Works with specified collection from MongoDB "acolyte"
* resolved using given Mongo DB.
*
* @param db Previously resolved Mongo DB
* @param name Collection name
* @param name $nameParam
* @param f Function applied to resolved Mongo collection
*
* {{{
* import reactivemongo.api.Collection
* import acolyte.reactivemongo.AcolyteDSL
*
* // handler: ConnectionHandler
* val s: Future[String] = AcolyteDSL.withFlatDB(handler) { db =>
* val s: Future[String] = AcolyteDSL.withDB(handler) { db =>
* AcolyteDSL.withCollection(db, "colName") { col =>
* "Result"
* }
* }
* }}}
*/
def withCollection[T](db: DB, name: String)(f: BSONCollection T)(implicit c: ExecutionContext): Future[T] = Future(f(db(name)))

/**
* Works with specified collection from MongoDB "acolyte"
* resolved using given driver initialized with Acolyte for ReactiveMongo
* (should not be used with other driver instances).
* Driver and associated resources are released
* after the function `f` the result `Future` is completed.
*
* @param conParam Connection manager parameter (see [[ConnectionManager]])
* @param name Collection name
* @param f Function applied to resolved Mongo collection
*
* {{{
* import reactivemongo.api.Collection
* import acolyte.reactivemongo.AcolyteDSL
*
* // handler: ConnectionHandler
* val i: Future[Int] =
* AcolyteDSL.withFlatCollection(handler, "colName") { col =>
* Future(1 + 2)
* }
* }}}
* @see AcolyteDSL.withFlatDB[A,B]
*/
def withFlatCollection[A, B](conParam: A, name: String)(f: BSONCollection Future[B])(implicit d: MongoDriver, m: ConnectionManager[A], c: ExecutionContext): Future[B] = withFlatDB(conParam) { db f(db(name)) }

/**
* Works with specified collection from MongoDB "acolyte"
* resolved using given connection.
*
* @param con Previously initialized connection
* @param name Collection name
* @param f Function applied to resolved Mongo collection
*
* {{{
* import reactivemongo.api.Collection
* import acolyte.reactivemongo.AcolyteDSL
*
* // handler: ConnectionHandler
* val i: Future[Int] = AcolyteDSL.withFlatConnection(handler) { con =>
* AcolyteDSL.withFlatCollection(con, "colName") { col =>
* Future(1 + 2)
* }
* }
* }}}
* @see WithDriver.withFlatDB[T]
*/
def withFlatCollection[T](con: MongoConnection, name: String)(f: BSONCollection Future[T])(implicit c: ExecutionContext): Future[T] = withFlatDB(con) { db f(db(name)) }

/**
* Works with specified collection from MongoDB "acolyte"
* resolved using given Mongo DB.
*
* @param db Previously resolved Mongo DB
* @param name Collection name
* @param f Function applied to resolved Mongo collection
*
* {{{
* import reactivemongo.api.Collection
* import acolyte.reactivemongo.AcolyteDSL
*
* // handler: ConnectionHandler
* val i: Future[Int] = AcolyteDSL.withFlatDB(handler) { db =>
* AcolyteDSL.withFlatCollection(db, "colName") { col =>
* Future(1 + 2)
* }
* }
* }}}
*/
def withFlatCollection[T](db: DB, name: String)(f: BSONCollection Future[T]): Future[T] = f(db(name))
def withCollection[T](
db: DB,
name: String)(
f: BSONCollection T): T = f(db.collection(name))

}

0 comments on commit a8cc52c

Please sign in to comment.