In the previous articles I built up a macro that generates a RESTful API from a case class model in a Play application. We now want to add a database to this API - in part 3 we used a simple map-based in memory data store, which wouldn’t really be that useful in the real world.

Step up MongoDB, a Scala library for interacting with it, Salat (which uses Casbah under the hood), and a Play plugin for making the integration with our Play application easier, Play-Salat. After following the install instructions, I’ve configured the play application to connect to Mongo:

# Disable default database plugins
dbplugin = disabled
evolutionplugin = disabled
ehcacheplugin = disabled
# Configure MongoDb
mongodb.default.db = "test"

We’ll use Play-Salat’s example Salat Context, and then we’ll create a set of traits that can be applied to the companion objects for our case classes. However, before we do that we need to change the case classes to use the Casbah ObjectId for their identifiers, so Author becomes:

case class Author(id: Option[ObjectId], name: String)
view raw Author.scala hosted with ❤ by GitHub

However, when you try and compile this, Play now gets upset because it doesn’t know how to turn an ObjectId into JSON. A quick read of the documentation and we see that we need to include an implicit Format[ObjectId] to do the conversion to/from JSON. We can implement this as follows:

implicit val objectIdFormat = new Format[ObjectId] {
def reads(json: JsValue): JsResult[ObjectId] = {
val s = json.as[String]
if (org.bson.types.ObjectId.isValid(s)) JsSuccess(new ObjectId(s)) else JsError("Not a valid ObjectId: " + s)
}
def writes(o: ObjectId) = JsString(o.toString())
}

However, we want this value to be available in the implicit scope in the middle of our macro that creates the formatter using the Play JSON macro, so how can we do that? We’ll look at a way using the macro itself. The way you might do this if you weren’t writing a macro is put the implicit value in an object, and then import myObject._ to get the value into scope wherever we use it. Well, we can easily define a class that can be instantiated to form such an object:

class ObjectIdFormat {
implicit val objectIdFormat = ...
}

Now we need to tell the macro what class it needs to use to get the implicit values - which we can use a type parameter for:

val regexesUsingMacro = restapi.Generator.macroPaths[ObjectIdFormat]

Which would be declared on the the macro:

def macroPaths[T] = macro macroPathsImpl[T]
def macroPathsImpl[T: c.WeakTypeTag](c: Context): c.Expr[Any] = ...

By declaring the T on the implementation of the macro as c.WeakTypeTag, we can use it within the macro to get at the actual class that is being used as a type parameter using the implicitly function:

implicitly[WeakTypeTag[T]].tpe.asInstanceOf[TypeRef].sym.asClass

This gives us the ClassSymbol for the type parameter, which we can then use in a Ident to get at the class’s constructor, define a value for it, and then import all its values:

val implicitsObject = c.fresh
val implicitClass = implicitly[WeakTypeTag[T]].tpe.asInstanceOf[TypeRef].sym.asClass
val implicitsObjectValue = ValDef(NoMods, implicitsObject, TypeTree(), Apply(Select(New(Ident(implicitClass)), CONSTRUCTOR), List()))
val importImplicits = Import(Ident(newTermName(implicitsObject)), List(ImportSelector(nme.WILDCARD, -1, nme.WILDCARD, -1)))

This is basically defining a value as the result of calling the no-argument constructor on the class from the type parameter. The result looks like this:

val $1$ = new ObjectIdFormat{<null>}{<null>}(){<null>};
import $1$._

So now our Play application compiles again. Let’s now move on to creating the Mongo DAOs. First off, we need an abstract class for our companion objects to extend. From the Salat docs, we can see that the class needs to extend ModelCompanion for type parameter T, and needs an implicit Manifest[T]. The SalatDAO requires that T extends AnyRef, so we’ll add a bound for the type parameter. We can then create a dao value that is created using a mongoCollection. All we need then is an abstract collectionName to use for each class’s collection:

abstract class MongoDb[T <: AnyRef](implicit m: Manifest[T]) extends ModelCompanion[T, ObjectId] {
def collectionName: String
val dao = new SalatDAO[T, ObjectId](collection = mongoCollection(collectionName)) {}
}
view raw MongoDb.scala hosted with ❤ by GitHub

Now we can create Mongo traits for the CRUD operations. Let’s start with Read:

trait MongoRead[T <: AnyRef] extends Read[T] {
val dao: SalatDAO[T, ObjectId]
def read(id: String) = dao.findOneById(new ObjectId(id))
}
view raw MongoRead.scala hosted with ❤ by GitHub

This is pretty simple - a template value is defined for the dao (that will be supplied by the MongoDb abstract class), and an Option for the object is found using the id. Let’s do another one - Delete is slightly more complicated, as we want to only delete if a record is found - a simple match expression should do the trick:

trait MongoDelete extends Delete {
val dao: SalatDAO[_, ObjectId]
def delete(id: String) = dao.findOneById(new ObjectId(id)) match {
case Some(t) => {
dao.removeById(new ObjectId(id))
true
}
case None => false
}
}

Now let’s look at Write. We’re need to take an object of type T, find the corresponding record, and update its state to that provided. To find the record we need the ID, but the object we have is of type T <: AnyRef, so we don’t have access to its ID. Simple enough - we introduced the WithId abstract class in the last article - we can update it to use ObjectId, and make sure the type parameter is bound to extend AnyRef:

abstract class WithId {
def id: Option[ObjectId]
}
view raw WithId.scala hosted with ❤ by GitHub

Now we can use this in the MongoWrite trait to get the id:

trait MongoWrite[T <: WithId[T]] extends Write[T] {
val dao: SalatDAO[T, ObjectId]
def write(t: T) = dao.findOneById(t.id.get) match {
case Some(_) => {
dao.save(t)
true
}
case None => false
}
}

I’m sure you’ve got the hang of these traits now - they’re pretty simple. Here’s the rest of those that we need:

trait MongoDeleteAll extends DeleteAll {
val dao: SalatDAO[_, ObjectId]
def deleteAll = dao.ids(MongoDBObject()).foreach(dao.removeById(_))
}
trait MongoCreate[T <: AnyRef] extends Create[T] {
val dao: SalatDAO[T, ObjectId]
def create(t: T) = dao.insert(t).map(_.toString)
}
trait MongoReplaceAll[T <: AnyRef] extends ReplaceAll[T] {
val dao: SalatDAO[T, ObjectId]
def replaceAll(ts: TraversableOnce[T]) = {
dao.find(MongoDBObject()).foreach(dao.remove)
ts.foreach(dao.insert)
}
}
trait MongoReadAll[T <: AnyRef] extends ReadAll[T] {
val dao: SalatDAO[T, ObjectId]
def readAll = dao.find(MongoDBObject()).toList
}

Finally, we add the new traits to the companion objects, adding just the ones we want for each case class:

case class Book(id: Option[ObjectId], title: String) extends WithId
object Book extends MongoDb[Book] with MongoRead[Book] with MongoReadAll[Book] {
override def collectionName = "books"
}
view raw Book.scala hosted with ❤ by GitHub

So that’s it - a RESTful API backed by MongoDB with the minimum of code using Scala def macros. I hope you’ve found this introduction interesting, and if you would like to browse the source code, please head over to Github.