Quick tutorial on Casbah, Scala’s MongoDB driver

(image from http://www.digitalcraftstudios.com)

For the past couple of months I have been playing with Scala, Casbah (Scala’s MongoDB driver) and Salat (a serialization library for json and bison).

For the most part, it’s been a fun experience, although the documentation on Casbah is not as great as I would have hoped. A guide does exists, but is not exhaustive. If like me you find yourself spending quite a bit of time looking for things not contained in it, here are some code snippets and I hope can act as a tutorial of sort on how to perform certain operations using Casbah.

Here it is. Please complete the poll below to help me improve my blog posts.

// Add salat and casbah to your build.scala or build.sbt files.
object ElBuild extends Build {
val Organization = "your own"
val Version = "0.0.1-SNAPSHOT"
val ScalaVersion = "2.10.3"
val ScalatraVersion = "2.2.1"
resolvers += "Maven Central Server" at "http://repo1.maven.org/maven2"
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
lazy val core = Project(
"core",
file("."),
settings = Defaults.defaultSettings ++ Seq(
organization := Organization,
name := "Your App",
version := Version,
scalaVersion := ScalaVersion,
parallelExecution in Test := false,
libraryDependencies ++= Seq(
"org.mongodb" %% "casbah" % "2.6.3",
"com.novus" %% "salat" % "1.9.4"
)
)
)
}

view raw
Build.scala
hosted with ❤ by GitHub

package tut
import org.bson.types.ObjectId
case class Instrument(name:String, proficiencyScore:Int)
case class Comment(commenter:ObjectId, comment:String, _id:ObjectId=new ObjectId())
case class Artist(name:String, band:String, fame:Int, instruments:List[Band]=List(), instrumentsCount=0, _id:ObjectId=null)
case class Song(title:String, comments:List[Comment]=List(), commentCount:Int=0,
artists:List[ObjectId]=List(), artistCount: Int=0, _id:ObjectId=null )
// for 'Artist' and 'Song', I default '_id' to null as it is meant to be set by casbah on insert and retrieval
// the reason why 'Comment' has an id defaulted to a new ObjectId is because it is a ValueObject, not an entity (See Domain-Driven-Design by Eric Evans)
// 'comments' and 'artists' are defaulted to a new list to ensure the field appears in the collection so as to avoid weird nullref exceptions

view raw
Entities.scala
hosted with ❤ by GitHub

import com.mongodb.casbah.{MongoClient, MongoClientURI}
import com.typesafe.config.ConfigFactory
object MongoFactory {
private val config = ConfigFactory.load()
private val DATABASE = config.getString("mongo.db")
private val server = MongoClientURI(config.getString("mongo.uri"))
private val client = MongoClient(server)
val database = client(DATABASE)
}

view raw
Factory.scala
hosted with ❤ by GitHub

package tut
import scala.Some
import com.mongodb.casbah.commons.conversions.scala._
import com.mongodb.casbah.WriteConcern
import com.mongodb.casbah.Imports._
import com.novus.salat._
import com.novus.salat.global._
import org.bson.types.ObjectId
import com.mongodb.CommandResult
class ArtistRepository {
val artistsCollection = MongoFactory.database("Artists")
def addNew(artist: Artist): Artist = {
val dBObject = grater[Artist].asDBObject(artist)
artistsCollection.save(dBObject, WriteConcern.Safe) //WriteConcern.Safe ensures things get written to immediately
grater[Artist].asObject(dBObject) // once save is called, dbObject gets populated with the '_id' field
}
//This shows how to perform a field value change
def changeName(id:ObjectId, newName:String)={
val query = MongoDBObject("_id" > id)
val update = $set("name" > newName)
val result = artistsCollection.findAndModify(query = query, update = update)
result match {
case Some(x) => //success case
case _ => //failure case
}
}
def getById(id: ObjectId): Option[Artist] = {
val query = MongoDBObject("_id" > id) // queries are in terms of MongoDBObject, which works almost like a map
val obj = artistsCollection.findOne(query)
obj match {
case Some(x) => Some(grater[Artist].asObject(x))
case _ => None
}
}
def findNotPlayingWithInstrument(intrument:String)={
//say we wanted to find any artist who couldnt play a given instrument
// we could use $ne for exact matches
val query = "intruments.name".$ne(intrument)
findAll(query)
}
//let's say that for whatever reason we want to find all artists either belong to a band or who could play a given instrument
def findByBandAndInstrument(band:String, instrument:String)={
//we could use the $or operator
val query = $or ( MongoDBObject("band"> band),
MongoDBObject("intruments.name"> instrument))
findAll(query)
}
private def findAll(query:MongoDBObject)={
val results = artistsCollection.find(query)
val artists = for (item < results) yield grater[Artist].asObject(item)
artists.toList
}
}

package tut
import scala.Some
import com.mongodb.casbah.commons.conversions.scala._
import com.mongodb.casbah.WriteConcern
import com.mongodb.casbah.Imports._
import com.novus.salat._
import com.novus.salat.global._
import org.bson.types.ObjectId
import com.mongodb.CommandResult
case class OperationResponse(hasSucceeded: Boolean, errorMessage: String = "")
class SongRepository {
val songsCollection = MongoFactory.database("Songs")
//This shows how to Add an item to collection
def addComment(id: ObjectId, comment: Comment): OperationResponse = {
val updateQuery = $addToSet("comments" > grater[Comment].asDBObject(comment)) ++
$inc("commentCount" > 1)
val query = MongoDBObject("_id" > id)
modify(query, updateQuery)
}
//Say for some reason we didn't like chatty commenter
def addOnlyOneCommentPerUser(id:ObjectId, comment:Comment): OperationResponse={
//we can refine the criteria using $not and $elemMatch
val notSameCommenter = "comments" $not { _ $elemMatch MongoDBObject("commenter">comment.commenter) }
val updateQuery = $addToSet("comments" > grater[Comment].asDBObject(comment)) ++
$inc("commentCount" > 1)
val query = MongoDBObject("_id" > id) ++ notSameCommenter
modify(query, updateQuery)
}
//This shows how to Remove an item from a collection
def removeComment(id: ObjectId, commentId: ObjectId): OperationResponse = {
val updateQuery = $pull("comments" > MongoDBObject("_id" > commentId)) ++ $inc("commentCount" > 1)
val query = MongoDBObject("_id" > id)
modify(query, updateQuery)
}
private def modify(query: MongoDBObject, update: MongoDBObject): OperationResponse = {
val result = songsCollection.findAndModify(query = query, update = update)
result match {
case Some(x) => OperationResponse(true)
case _ => OperationResponse(false, s"Operation Failed")
}
}
}

Quick tutorial on Casbah, Scala’s MongoDB driver

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s