The goal of ReactiveMongo Extensions is to provide all the necessary tools for ReactiveMongo other than the core functionality.
This project has moved to https://github.com/ReactiveMongo/ReactiveMongo-Extensions
Here is a complete example using Play 2.2, ReactiveMongo Extensions and Flight.js https://github.com/mertkavi/play-reactivemongo-extensions-sample
ReactiveMongo Extensions comes as 2 separate packages which are reactivemongo-extensions-bson and reactivemongo-extensions-json. reactivemongo-extensions-bson package targets ReactiveMongo, while reactivemongo-extensions-json targets Play-ReactiveMongo.
DAOs provide an abstraction layer on top of ReactiveMongo adding higher level APIs like findOne, findById, count, foreach, fold, etc. ReactiveMongo Extensions currently provides two DAO types: reactivemongo.extensions.dao.BsonDao for BSONCollection and reactivemongo.extensions.dao.JsonDao for JSONCollection.
You will need to define a DAO for each of your models(case classes).
Below is a sample model.
import reactivemongo.bson._ import reactivemongo.extensions.dao.Handlers._ case class Person( _id: BSONObjectID = BSONObjectID.generate, name: String, surname: String, age: Int) object Person { implicit val personHandler = Macros.handler[Person] }To define a BsonDao for the Person model you just need to extend BsonDao.
import reactivemongo.api.{ MongoDriver, DB } import reactivemongo.bson.BSONObjectID import reactivemongo.bson.DefaultBSONHandlers._ import reactivemongo.extensions.dao.BsonDao import scala.concurrent.ExecutionContext.Implicits.global object MongoContext { val driver = new MongoDriver val connection = driver.connection(List("localhost")) def db(): DB = connection("reactivemongo-extensions") } object PersonDao extends BsonDao[Person, BSONObjectID](MongoContext.db, "persons")From now on you can insert a Person instance, find it by id, find a random person or etc.
val person1 = Person(name = "foo", surname = "bar", age = 16) val person2 = Person(name = "fehmi can", surname = "saglam", age = 32) val person3 = Person(name = "ali", surname = "veli", age = 64) PersonDao.insert(person1) PersonDao.insert(Seq(person2, person3)) PersonDao.findById(person1._id) PersonDao.findRandom(BSONDocument("age" -> BSONDocument("$ne" -> 16)))Read more about BsonDao here and JsonDao here.
There are also DSL helpers for each DAO type, which are reactivemongo.extensions.dsl.BsonDsl and reactivemongo.extensions.json.dsl.JsonDsl. DSL helpers provide utilities to easily construct JSON or BSON queries.
By mixing in or importing BsonDsl you could write the query above like this:
import reactivemongo.extensions.dsl.BsonDsl._ PersonDao.findRandom("age" $gt 16 $lt 32)Read more about Query DSL here.
The Criteria DSL provides the ablity to formulate queries thusly:
// Using an Untyped.criteria { import Untyped._ // The MongoDB properties referenced are not enforced by the compiler // to belong to any particular type. This is what is meant by "Untyped". val adhoc = criteria.firstName === "Jack" && criteria.age >= 18; val cursor = collection.find(adhoc).cursor[BSONDocument]; } { // Using a Typed criteria which restricts properties to the // given type. import Typed._ case class ExampleDocument (aProperty : String, another : Int) val byKnownProperties = criteria[ExampleDocument].aProperty =~ "^[A-Z]\\w+" && criteria[ExampleDocument].another > 0; val cursor = collection.find(byKnownProperties).cursor[BSONDocument]; }Read more about Criteria DSL here.
By defining a life cycle object, one can preprocess all models before being persisted or perform specific actions after life cycle events. This can be useful for updating temporal fields on all model instances before persisting.
import reactivemongo.bson._ import reactivemongo.extensions.dao.LifeCycle import reactivemongo.extensions.dao.Handlers._ import reactivemongo.extensions.util.Logger import org.joda.time.DateTime case class TemporalModel( _id: BSONObjectID = BSONObjectID.generate, name: String, surname: String, createdAt: DateTime = DateTime.now, updatedAt: DateTime = DateTime.now) object TemporalModel { implicit val temporalModelFormat = Macros.handler[TemporalModel] implicit object TemporalModelLifeCycle extends LifeCycle[TemporalModel, BSONObjectID] { def prePersist(model: TemporalModel): TemporalModel = { Logger.debug(s"prePersist $model") model.copy(updatedAt = DateTime.now) } def postPersist(model: TemporalModel): Unit = { Logger.debug(s"postPersist $model") } def preRemove(id: BSONObjectID): Unit = { Logger.debug(s"preRemove $id") } def postRemove(id: BSONObjectID): Unit = { Logger.debug(s"postRemove $id") } def ensuredIndexes(): Unit = { Logger.debug("ensuredIndexes") } } }ReactiveMongo Extensions support auto indexes which ensures indexes on DAO load.
object PersonDao extends { override val autoIndexes = Seq( Index(Seq("name" -> IndexType.Ascending), unique = true, background = true), Index(Seq("age" -> IndexType.Ascending), background = true) ) } with BsonDao[Person, BSONObjectID](MongoContext.db, "persons") You can override writeConcern in your DAO definition which defaults to GetLastError().
object PersonDao extends BsonDao[Person, BSONObjectID](MongoContext.db, "persons") { override def defaultWriteConcern = GetLastError(j = true) }You can define your fixtures using HOCON. Lexical scopes are supported in addition to HOCON spec.
persons.conf
_predef { country: TC } # "persons" collection persons { person1 { _id: _id_person1 name: Ali surname: Veli fullname: ${name} ${surname} age: 32 salary: 999.85 time: 12345678900 country: ${_predef.country} } person2 { _id: _id_person2 name: Haydar surname: Cabbar fullname: ${name} ${surname} age: ${person1.age} salary: { "$double": 1000.0 } time: 12345678999 country: ${_predef.country} } } events.conf
# Predefined reusable values _predef { location: { city: Ankara place: Salon } } # "events" collection events { event1 { _id: _id_event1 title: Developer workshop organizer: ${persons.person1.fullname} location: ${_predef.location} } } After defining your fixtures you can load them using BsonFixtures or JsonFixtures.
import reactivemongo.extensions.bson.fixtures.BsonFixtures BsonFixtures(db).load("persons.conf", "events.conf")While composing futures with for comprehensions, handling option values can be cumbersome. ~ operator converts a Future[Option[T]] to Future[T]. It throws a java.util.NoSuchElementException if the Option is None. Then you can check the exception in Future.recover.
import reactivemongo.extensions.Implicits._ (for { model1 <- ~dao.findOne("none" $eq "unknown") model2 <- ~dao.findOne("none" $eq "unknown") result <- compute(model1, model2) } yield result) recover { case ex: java.util.NoSuchElementException => println("Option is None") throw ex } The general format is that release a.b.c.d is compatible with ReactiveMongo a.b.c. Current version matrix is below:
| reactivemongo-extensions-bson | Target ReactiveMongo version |
|---|---|
| 0.10.0.4 | 0.10.0 |
| 0.10.0.5-SNAPSHOT | 0.10.0 |
| reactivemongo-extensions-json | Target Play-ReactiveMongo version |
|---|---|
| 0.10.0.4 | 0.10.2 |
| 0.10.0.5-SNAPSHOT | 0.10.2 |
Note: Only available for scala 2.10.
If you use SBT, you just have to edit build.sbt and add the following:
libraryDependencies ++= Seq( "net.fehmicansaglam" %% "reactivemongo-extensions-bson" % "0.10.0.4" )Or if you want to be on the bleeding edge using snapshots:
resolvers += "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/" libraryDependencies ++= Seq( "net.fehmicansaglam" %% "reactivemongo-extensions-bson" % "0.10.0.5-SNAPSHOT" )Contributions are always welcome. Good ways to contribute include:
- Raising bugs and feature requests
- Fixing bugs
- Improving the performance
- Adding to the documentation