Skip to content
This repository

Using Salat with Play 2.0

Disclaimer: I don't know anything about the Play framework! But I can try to help you if Salat doesn't work with Play.

Play Plugin

Thanks to Leon Radley for this cool Play plugin!

leon/play-salat

Sample Projects

I can't thank members of the Play community enough for their help in making Salat work with Play framework.

And thanks again to Aaron White for his help with Play 1.2. (That page is now outdated, but is available in the wiki history if you are interested.)

A simple model companion

Salat provides a ModelCompanion trait to provide easy access to serialization and DAO methods from your model companion object.

What do you get?

  • convenience methods to convert a model object
    • to and from a DBObject
    • JSON support
      • to JSON: choose between pretty output, compact output or a lift-json JObject
      • from JSON: convert a string or a lift-json JObject to a model object
    • to and from a Map[String, Any]
  • convenience methods for CRUD operations
    • insert
    • save
    • update
    • remove
  • convenience methods for querying
    • typed methods for finding using an ID or a list of IDs
    • count using query criteria or just specifying fields that must be present or absent in your documents
    • findAll() returns an Iterator typed to your model object

What do you need to do?

Create a companion object to your model object, and extend ModelCompanion[ObjectType, ID].

ModelCompanion[ObjectType, ID] defines def dao: DAO[ObjectType, ID].

DAO[ObjectType, ID] is a top-level DAO interface. You will probably want to supply an instance of SalatDAO[ObjectType, ID], but as long as you implement DAO[ObjectType, ID] you can supply whatever you want. For instance, you might choose to extend SalatDAO[ObjectType, ID] to create MyCustomDAO[ObjectType, ID].

It is your choice to supply the dao instance as a def, a val, or a lazy val in your model companion.

Model companion example

In the example below, we are using a Context provided by Salat. If you want to use your own custom context, replace import com.novus.salat.global._ with an import for your own context.

package model

import com.novus.salat.global._
import com.novus.salat.annotations._
import com.mongodb.casbah.Imports._
import org.scala_tools.time.Imports._
import com.novus.salat.dao.{ SalatDAO, ModelCompanion }

object MyModel extends ModelCompanion[MyModel, ObjectId] {
  val collection = MongoConnection()("my_db")("my_model_coll")
  val dao = new SalatDAO[MyModel, ObjectId](collection = collection) {}
}

case class MyModel(@Key("_id") id: ObjectId,
                   x: String,
                   y: Int,
                   z: List[Double],
                   d: DateTime)

Usage Examples

See ModelCompanionSpec.

val _id = new ObjectId
val d = new DateTime
val x = "Test"
val y = 99
val z = 1d :: 2d :: 3d :: Nil
val m = MyModel(id = _id, x = x, y = y, z = z, d = d)

scala> val m = MyModel(id = _id, x = x, y = y, z = z, d = d)
m: model.MyModel = MyModel(4f8ce7f94cea76aefbcd1b6e,Test,99,List(1.0, 2.0, 3.0),2012-04-16T23:48:09.640-04:00)

Conversion convenience methods

DBObject support

From model object to DBObject

scala> val dbo = MyModel.toDBObject(m)
dbo: com.mongodb.casbah.Imports.DBObject = { "_typeHint" : "model.MyModel" , "_id" : { "$oid" : "4f8ce7f94cea76aefbcd1b6e"} , "x" : "Test" , "y" : 99 , "z" : [ 1.0 , 2.0 , 3.0] , "d" : { "$date" : "2012-04-17T03:48:09Z"}}

From DBObject to model object

scala> MyModel.toObject(dbo)
res2: model.MyModel = MyModel(4f8ce7f94cea76aefbcd1b6e,Test,99,List(1.0, 2.0, 3.0),2012-04-16T23:48:09.640-04:00)

JSON support

From model object to pretty JSON

scala> MyModel.toPrettyJson(m)
res3: String =
{
  "_typeHint":"model.MyModel",
  "_id":{
    "$oid":"4f8ce7f94cea76aefbcd1b6e"
  },
  "x":"Test",
  "y":99,
  "z":[1.0,2.0,3.0],
  "d":"2012-04-17T03:48:09Z"
}

From model object to compact JSON

scala> MyModel.toCompactJson(m)
res4: String = {"_typeHint":"model.MyModel","_id":{"$oid":"4f8ce7f94cea76aefbcd1b6e"},"x":"Test","y":99,"z":[1.0,2.0,3.0],"d":"2012-04-17T03:48:09Z"}

From model object to lift-json JObject

scala> MyModel.toJson(m)
res5: net.liftweb.json.JsonAST.JObject = JObject(List(JField(_typeHint,JString(model.MyModel)), JField(_id,JObject(List(JField($oid,JString(4f8ce7f94cea76aefbcd1b6e))))), JField(x,JString(Test)), JField(y,JInt(99)), JField(z,JArray(List(JDouble(1.0), JDouble(2.0), JDouble(3.0)))), JField(d,JString(2012-04-17T03:48:09Z))))

From JSON to model object

scala> val s = """{
     |   "_typeHint":"model.MyModel",
     |   "_id":{
     |     "$oid":"4f8ce7f94cea76aefbcd1b6e"
     |   },
     |   "x":"Test",
     |   "y":99,
     |   "z":[1.0,2.0,3.0],
     |   "d":"2012-04-17T03:48:09Z"
     | }"""
s: java.lang.String =
{
  "_typeHint":"model.MyModel",
  "_id":{
    "$oid":"4f8ce7f94cea76aefbcd1b6e"
  },
  "x":"Test",
  "y":99,
  "z":[1.0,2.0,3.0],
  "d":"2012-04-17T03:48:09Z"
}

scala> MyModel.fromJSON(s)
res0: model.MyModel = MyModel(4f8ce7f94cea76aefbcd1b6e,Test,99,List(1.0, 2.0, 3.0),2012-04-17T03:48:09.000Z)

For examples of using a lift-json JObject, refer to ModelCompanionSpec.

Map support

From model object to Map[String, Any]

scala> MyModel.toMap(m)
res1: Map[String,Any] = Map((x,Test), (_typeHint,com.novus.salat.test.dao.MyModel), (y,99), (_id,4f8ce7f94cea76aefbcd1b6e), (z,List(1.0, 2.0, 3.0)), (d,2012-04-16T23:47:09.640-04:00))

From Map[String, Any] to model object

scala> val m = Map("_id" -> _id, "x" -> x, "y" -> y, "z" -> z, "d" -> d)
m: scala.collection.immutable.Map[java.lang.String,Any] = Map((x,Test), (y,99), (_id,4f8ce7f94cea76aefbcd1b6e), (z,List(1.0, 2.0, 3.0)), (d,2012-04-16T23:47:09.640-04:00))

scala> MyModel.fromMap(m)
res2: model.MyModel = MyModel(4f8ce7f94cea76aefbcd1b6e,Test,99,List(1.0, 2.0, 3.0),2012-04-16T23:47:09.640-04:00)

CRUD shorthand

Insert

scala> val someId = MyModel.insert(m)
someId: Option[com.mongodb.casbah.Imports.ObjectId] = Some(4f8ce7f94cea76aefbcd1b6e)

scala> someId == Some(_id)
res6: Boolean = true

Save

scala> MyModel.save(m.copy(z = 0d :: z))

scala> MyModel.findOneByID(_id)
res8: Option[model.MyModel] = Some(MyModel(4f8ce7f94cea76aefbcd1b6e,Test,99,List(0.0, 1.0, 2.0, 3.0),2012-04-16T23:48:09.640-04:00))

Update

scala> MyModel.update(q = MongoDBObject("_id" -> _id),
     |     o = MongoDBObject("$set" -> MongoDBObject("x" -> "Test2")),
     |     upsert = false, multi = false, wc = MyModel.dao.collection.writeConcern)

scala> MyModel.findOneByID(_id)
res10: Option[model.MyModel] = Some(MyModel(4f8ce7f94cea76aefbcd1b6e,Test2,99,List(0.0, 1.0, 2.0, 3.0),2012-04-16T23:48:09.640-04:00))

Remove

scala> MyModel.insert(m)
res17: Option[com.mongodb.casbah.Imports.ObjectId] = Some(4f8ce7f94cea76aefbcd1b6e)

scala> MyModel.findOneByID(_id)
res18: Option[model.MyModel] = Some(MyModel(4f8ce7f94cea76aefbcd1b6e,Test,99,List(1.0, 2.0, 3.0),2012-04-16T23:48:09.640-04:00))

scala> MyModel.remove(m)

scala> MyModel.findOneByID(_id)
res20: Option[model.MyModel] = None

Querying

Count

scala> MyModel.count(MongoDBObject("x" -> "Test"))
res3: Long = 1

Find

SalatMongoCursor[ObjectType] is a MongoDB cursor typed to ObjectType. You can convert it to an iterator, a list or any other type of collection.

The only difference between SalatMongoCursor and MongoCursor is that SalatMongoCursor takes care of deserialization boilerplate so you don't have to.

scala> MyModel.find(MongoDBObject("x" -> "Test"))
res5: com.novus.salat.dao.SalatMongoCursor[model.MyModel] = non-empty iterator

scala> MyModel.find(MongoDBObject("x" -> "Test")).toList
res6: List[model.MyModel] = List(MyModel(4f8ce7f94cea76aefbcd1b6e,Test,99,List(1.0, 2.0, 3.0),2012-04-16T23:48:09.640-04:00))

Find One

scala> MyModel.findOne(MongoDBObject("x" -> "Test"))
res10: Option[model.MyModel] = Some(MyModel(4f8ce7f94cea76aefbcd1b6e,Test,99,List(1.0, 2.0, 3.0),2012-04-16T23:48:09.640-04:00))

Find One by ID

scala> MyModel.findOneByID(_id)
res4: Option[model.MyModel] = Some(MyModel(4f8ce7f94cea76aefbcd1b6e,Test,99,List(1.0, 2.0, 3.0),2012-04-16T23:48:09.640-04:00))

Find IDs

scala> MyModel.ids(MongoDBObject("x" -> "Test"))
res11: List[com.mongodb.casbah.Imports.ObjectId] = List(4f8ce7f94cea76aefbcd1b6e)

Projection

  • primitiveProjection[P] brings back Option[P] where P is a type whose deserialization is handled by the mongo-java-driver or Casbah. Examples include Int, Double, String, and DateTime.
  • projection[P] brings back Option[P] where P is a case class, trait or abstract superclass whose serialization is handled by Salat.
scala> MyModel.primitiveProjection[Int](MongoDBObject("x" -> "Test"), "y")
res12: Option[Int] = Some(99)

Projection list

  • primitiveProjections[P] brings back List[P] where P is a type whose deserialization is handled by the mongo-java-driver or Casbah. Examples include Int, Double, String, and DateTime.
  • projections[P] brings back List[P] where P is a case class, trait or abstract superclass whose serialization is handled by Salat.
scala> MyModel.primitiveProjections[Int](MongoDBObject("x" -> "Test"), "y")
res13: List[Int] = List(99)

Find all

findAll() returns Iterator[ObjectType] by default.

scala> MyModel.findAll()
res14: Iterator[model.MyModel] = non-empty iterator

scala> MyModel.findAll().toList  // probably a bad idea!
res15: List[model.MyModel] = List(MyModel(4f8ce7f94cea76aefbcd1b6e,Test,99,List(1.0, 2.0, 3.0),2012-04-16T23:48:09.640-04:00))

Troubleshooting

  • first, restart
  • second, clean and then restart
  • third, try using ModelCompanion
  • as a last resort, you can brute force the issue by adding a call to Context#clearAllGraters() in GlobalSettings#onStart

ClassCastException

Make sure that all your DAOs are declared as val in ModelCompanion implementations.

IncompatibleClassChangeError

If you encounter runtime exceptions like this, try creating a custom Salat context:

IncompatibleClassChangeError: Class myApp.models.Event does not implement the requested interface myApp.models.Endable

First, create a package object with your custom Salat context, and import that everywhere instead.

import com.novus.salat._
import play.api._
import play.api.Play.current

package object myApp {
  implicit val ctx = {
    val c = new Context() {
      val name = "Custom Context"
    }
    c.registerClassLoader(Play.classloader)
    c
  }
}

Then make sure to remove everywhere you have imported the out-of-box default Salat context:

import com.novus.salat.global._

and replace it with an import of your custom context:

import myApp._

Thanks to ornicar for contributing this tip to the wiki!

Something went wrong with that request. Please try again.