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:
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:
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:
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:
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:
Which would be declared on the the macro:
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:
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:
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:
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:
Now we can create Mongo traits for the CRUD operations. Let’s start with Read
:
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:
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
:
Now we can use this in the MongoWrite
trait to get the id:
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:
Finally, we add the new traits to the companion objects, adding just the ones we want for each case class:
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.