Skip to content

Commit

Permalink
Merge pull request #68 from fommil/master
Browse files Browse the repository at this point in the history
small API changes
  • Loading branch information
fommil committed Mar 22, 2013
2 parents 4c44854 + 857681f commit d8570a0
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 34 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ val entity = ...

val crud = new SprayMongo

crud.create(entity)
crud.insert(entity)
crud.findAndUpdate("id":>entity.id, "$set":>{"name":>"Bar"})
val update = crud.findOne("id":>entity.id)

val update = entity.copy(name = "Bar")
crud.findAndReplace("id":>entity.id, update)

val morePopular = crud.searchAll("count":> {"$gte":> update.count}) // awesome DSL for JSON
val popular = crud.find("count":> {"$gte":> update.count}) // awesome DSL for JSON
```

However, anybody using this library is strongly encouraged to read the [MongoDB Documentation](https://docs.mongodb.org/manual/) as it is often necessary to get close to the raw queries to understand what is happening, especially the [Aggregation Framework](https://docs.mongodb.org/manual/applications/aggregation/).
Expand Down
10 changes: 8 additions & 2 deletions src/main/scala/org/cakesolutions/scalad/mongo/MongoCreate.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
package org.cakesolutions.scalad.mongo

import com.mongodb.WriteConcern

/** `CREATE` operations. */
trait MongoCreate {

/** Use unique indices in MongoDB to ensure that duplicate entries are not created
* (`CollectionProvider` is a good place to do this).
* @return the parameter, or `None` if not added.
*/
def create[T: CollectionProvider : MongoSerialiser](entity: T): Option[T] = {
def create[T: CollectionProvider : MongoSerialiser](entity: T, concern: WriteConcern = null): Option[T] = {
val collection = implicitly[CollectionProvider[T]].getCollection
val serialiser = implicitly[MongoSerialiser[T]]

val result = collection.insert(serialiser serialiseDB entity).getLastError
val serialised = serialiser serialiseDB entity
val result =
if (concern == null ) collection.insert(serialised).getLastError
else collection.insert(serialised, concern).getLastError

if (result.ok()) Some(entity)
else None
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package org.cakesolutions.scalad.mongo.sprayjson
import org.cakesolutions.scalad.mongo._
import spray.json.{JsValue, JsObject, JsonFormat}
import akka.contrib.jul.JavaLogging
import com.mongodb.{DB, DBObject}
import com.mongodb.{WriteConcern, DB, DBObject}
import spray.json.pimpAny
import scala.language.implicitConversions

Expand All @@ -19,17 +19,18 @@ trait Implicits extends SprayJsonConvertors {
/** MongoDB format for indexing fields, e.g. {"key": 1} */
class SprayMongoCollection[T](db: DB,
name: String,
indexFields: List[JsObject] = Nil,
uniqueFields: List[JsObject] = Nil)
uniqueIndexes: JsObject*)
extends CollectionProvider[T] with Implicits with JavaLogging {

def getCollection = db.getCollection(name)

if (IndexedCollectionProvider.privilegedIndexing(getCollection)) {
log.debug("Ensuring indexes exist on " + getCollection)
uniqueFields.foreach(field => getCollection.ensureIndex(field, null, true))
indexFields.foreach(field => getCollection.ensureIndex(field, null, false))
uniqueIndexes.foreach(field => getCollection.ensureIndex(field, null, true))
indexes.foreach(field => getCollection.ensureIndex(field, null, false))
}

def indexes: List[JsObject] = Nil
}

/** Forwards all requests to the ScalaD API, independent of the Java MongoDB API.
Expand All @@ -41,22 +42,31 @@ class SprayMongo extends Implicits with JavaLogging {

private val scalad = new MongoCrud

def create[T: CollectionProvider : JsonFormat](entity: T): Option[T] = scalad.create(entity)
def insert[T: CollectionProvider : JsonFormat](entity: T): Option[T] = scalad.create(entity)

// good for fire-and-forget writes that happen often, e.g. writing logs
def insertFast[T: CollectionProvider : JsonFormat](entity: T): Option[T] = scalad.create(entity, WriteConcern.UNACKNOWLEDGED)

def searchFirst[T: CollectionProvider : JsonFormat](query: JsObject): Option[T] = scalad.searchFirst(query)
def findOne[T: CollectionProvider : JsonFormat](query: JsObject): Option[T] = scalad.searchFirst(query)

def searchAll[T: CollectionProvider : JsonFormat](query: JsObject): ConsumerIterator[T] = scalad.searchAll(query)
def find[T: CollectionProvider : JsonFormat](query: JsObject): ConsumerIterator[T] = scalad.searchAll(query)

def findAndModify[T: CollectionProvider](query: JsObject, rule: JsObject) = scalad.findAndModify(query, rule)

def findAndReplace[T: CollectionProvider : JsonFormat](query: JsObject, update: T) = scalad.findAndModify(query, update.toJson)

def deleteFirst[T: CollectionProvider : JsonFormat](query: JsObject): Option[T] = {
def removeOne[T: CollectionProvider : JsonFormat](query: JsObject): Option[T] = {
val result = implicitly[CollectionProvider[T]].getCollection.findAndRemove(query)
if (result == null) None
else Some(serialiser[T] deserialise result)
}

def count[T: CollectionProvider : JsonFormat](query: JsObject): Long =
implicitly[CollectionProvider[T]].getCollection.count(query)

def count[T: CollectionProvider : JsonFormat](): Long =
implicitly[CollectionProvider[T]].getCollection.count()

// note, mongodb 2.3.x introduced a lot of fixes to the aggregation framework,
// e.g. allowing for binary data to be included in pipelines.
// https://github.com/janm399/scalad/issues/63
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package org.cakesolutions.scalad.mongo.sprayjson

import java.util.UUID
import org.specs2.mutable.Specification
import spray.json.JsNull
import spray.json.{JsObject, JsNull}
import com.mongodb.MongoException

class PersistenceSpec extends Specification with SprayJsonTestSupport with NullMarshalling {
Expand All @@ -22,56 +22,60 @@ class PersistenceSpec extends Specification with SprayJsonTestSupport with NullM

val modified = student.copy(graduated = true)

implicit val StudentCollectionProvider = new SprayMongoCollection[Student](db, "students", List("collegeUuid":>1), List("id":>1))
implicit val StudentCollectionProvider = new SprayMongoCollection[Student](db, "students", "id":>1) {
override def indexes: List[JsObject] = {"collegeUuid":>1} :: Nil
}

"SprayJsonSerialisation" should {

"ensure a Student is created" in {
crud.create(student) === Some(student)
crud.insert(student) === Some(student)
}

"ensure the uniqueness constraint is respected" in {
crud.create(student) should throwA[MongoException]
crud.insert(student) should throwA[MongoException]
}

"ensure a Student is searchable by id" in {
crud.searchFirst[Student]("id":>student.id) === Some(student)
crud.findOne[Student]("id":>student.id) === Some(student)
}

"ensure a Student is searchable by name" in {
crud.searchFirst[Student]("name":>student.name) === Some(student)
crud.findOne[Student]("name":>student.name) === Some(student)
}

"ensure a Student is searchable by UUID" in {
crud.searchFirst[Student]("collegeUuid":>student.collegeUuid) === Some(student)
crud.findOne[Student]("collegeUuid":>student.collegeUuid) === Some(student)
}

"ensure a Student is searchable by nested JSON query" in {
crud.searchFirst[Student]("address":> student.address) === Some(student)
crud.searchFirst[Student]("address":> {"road":> student.address.road <> "number":> student.address.number}) === Some(student)
crud.findOne[Student]("address":> student.address) === Some(student)
crud.findOne[Student]("address":> {"road":> student.address.road <> "number":> student.address.number}) === Some(student)
}

"ensure a Student is modifyable" in {
crud.findAndModify[Student]("id":>student.id, "$set":>{"graduated":> modified.graduated})
crud.searchFirst[Student]("id":>student.id) === Some(modified)
crud.findOne[Student]("id":>student.id) === Some(modified)
}

"ensure a Student is replaceable" in {
crud.findAndReplace[Student]("id":>student.id, student)
crud.searchFirst[Student]("id":>student.id) === Some(student)
crud.findOne[Student]("id":>student.id) === Some(student)
}

"ensure a Student can be deleted" in {
crud.deleteFirst[Student]("id":>student.id)
crud.searchFirst[Student]("id":>student.id) === None
crud.removeOne[Student]("id":>student.id)
crud.findOne[Student]("id":>student.id) === None
}

"ensure we can run aggregate queries on Students" in {
crud.create(student)
crud.create(student.copy(id = 1))
crud.create(student.copy(id = 2))
crud.create(student.copy(id = 3, name = "Evil Alfredo"))
crud.create(student.copy(id = 4, collegeUuid = UUID.randomUUID()))
crud.insert(student)
crud.insert(student.copy(id = 1))
crud.insert(student.copy(id = 2))
crud.insert(student.copy(id = 3, name = "Evil Alfredo"))
crud.insert(student.copy(id = 4, collegeUuid = UUID.randomUUID()))

// this could be achieved with a count()... it's just a POC
crud.aggregate[Student](
"$match":> {"name":> "Alfredo"},
"$match":> {"collegeUuid" :> student.collegeUuid},
Expand All @@ -80,5 +84,9 @@ class PersistenceSpec extends Specification with SprayJsonTestSupport with NullM
) === List({"count":> 3})
// if this fails, you might be running < mongo 2.3.x
}

"ensure Students can be counted" in {
crud.count[Student]("id":>{"$exists":>true}) === 5
}
}
}

0 comments on commit d8570a0

Please sign in to comment.