Usually a data access object for MongoDB consists of common routine CRUD methods. Those methods should be implemented, tested, maintained just like any other code. In this post, I’m going to show you how to use SalatDAO
to vastly simplify the process.
Generally, Salat is used for serialization of case classes, enums and collections to MongoDBObject
(a key-value map which can be saved to the database) and back. Salat uses Casbah library as an interface for MongoDB. Also, Salat provides the SalatDAO
class. By extending this class, you can add a set of basic CRUD methods to your DAO. See these methods with comments here. Also, wiki might be helpful.
Let me demonstrate the suggested approach on the example of the DAO for the Chocolate
case class.
Here is the Chocolate
case class:
import com.mongodb.casbah.Imports.ObjectId
case class Chocolate(_id: ObjectId = new ObjectId,
name: String,
ingredients: String,
producer: String)
And here is the data access object for the Chocolate
:
import com.mongodb.casbah.Imports._
import com.novus.salat.dao.SalatDAO
import com.novus.salat.global._
import com.sysgears.example.domain.Chocolate
class ChocolateDAO extends SalatDAO[Chocolate, ObjectId]
(collection= MongoConnection()("chocolate_base")("chocolate"))
In order to extend SalatDAO
, you need to specify the case class type (Chocolate
), the id field type (ObjectId
) and initialize the MongoDB collection. In this example "chocolate_base"
is a MongoDB database name, and "chocolate"
– the collection name.
Now, ChocolateDAO
contains all the methods from the BaseDAOMethods
trait. However, many of those methods take MongoDBObject
as a parameter. It is a flaw in this approach: using these DAO methods makes your code database specific. The easy and handy way to avoid this flaw is to use a case class with the implicit conversion instead of MongoDBObject
.
Also, in order to build queries in an agile way, we will add a class that will be pretty similar to the Chocolate
case class, but with a field type changed to Option
and a default value set to None
. That’s how we will be able to search only by specific fields:
import com.mongodb.casbah.Imports.ObjectId
case class ChocolateQueryParams(_id: Option[ObjectId] = None,
name: Option[String] = None,
ingredients: Option[String] = None,
producer: Option[String] = None)
Here is an example of implicit conversions for the Chocolate
case class and ChocolateQueryParams
:
import com.mongodb.casbah.Imports._
import com.novus.salat._
import com.novus.salat.global._
import com.sysgears.example.domain.{Chocolate, ChocolateQueryParams}
import scala.language.implicitConversions
object ChocolateConversions {
implicit def paramsToDBObject(params: ChocolateQueryParams): DBObject =
grater[ChocolateQueryParams].asDBObject(params)
implicit def chocolateToDBObject(c: Chocolate): DBObject =
grater[Chocolate].asDBObject(c)
}
Code language: JavaScript (javascript)
These conversions will help us to use case classes instead of MongoDBObject
in SalatDAO
method params.
Now, you can use all the SalatDAO
methods in the much simpler way (see the example):
import com.sysgears.example.dao.ChocolateConversions._
import com.sysgears.example.dao.ChocolateDAO
import com.sysgears.example.domain.{Chocolate, ChocolateQueryParams}
object Main extends App {
val chocolateDAO = new ChocolateDAO
// Data for test:
val entry = Chocolate(name = "Chocolate santa",
ingredients = "cacao, mushrooms, milk",
producer = "Kind Chocolate")
val updateEntry = entry.copy(producer = "Wonka's Chocolate Factory")
// Create:
val id = chocolateDAO.insert(entry)
assert(id.isDefined)
// Read:
val getResult = chocolateDAO.findOneById(id.get)
assert(getResult.isDefined)
assert(getResult.get == entry.copy(_id = id.get))
// Update:
chocolateDAO.update(ChocolateQueryParams(name = Some("Chocolate santa")),
updateEntry)
assert(chocolateDAO.find(
ChocolateQueryParams(producer = Some("Wonka's Chocolate Factory"))).nonEmpty)
// Delete:
chocolateDAO.removeById(id.get)
assert(chocolateDAO.findOneById(id.get).isEmpty)
}
Code language: JavaScript (javascript)
Basically, we’ve got CRUD methods for the DAO without implementing any of these methods. This approach will help you to avoid mistakes in the DAO code and save your time. Summarizing the flow, all you need to do is to:
- create a case class that represents your model
- add a similar case class (that will help to build queries) with a field type changed from
X
toOption[X]
and set a default value toNone
- create DAO class that extends
SalatDAO
- implement implicit conversions to convert the created case classes into
MongoDBObject
All the sources are available on the GitHub repository.