Mongo Record Basics
Define record class
import net.liftweb.mongodb.record.{MongoMetaRecord, MongoRecord}
import net.liftweb.record.field.{IntField, StringField}
import net.liftweb.mongodb.record.field.ObjectIdPk
class MainDoc private() extends MongoRecord[MainDoc] with ObjectIdPk[MainDoc] {
def meta = MainDoc
object name extends StringField(this, 12)
object cnt extends IntField(this)
}
object MainDoc extends MainDoc with MongoMetaRecord[MainDoc]
Create record instance and save
val md1 = MainDoc.createRecord
.name("md1")
.cnt(5)
.save
Find instance
val md = MainDoc.find("_id", md1.id.is)
MongoPk
All MongoRecords must define an id field. Currently, this is type Any, but it will be changed in a future version to MandatoryTypedField.
The MongoPk series of traits all provide an id for you. These all save the id field as _id in the database. ObjectId, UUID, String, Int, and Long implementations are available. If you require something more complicated, you can define the id field yourself.
Example id using a BsonRecord:
import net.liftweb.mongodb._
class MyPk private() extends BsonRecord[MyPk] {
def meta = MyPk
object keyA extends StringField(this, 100)
object keyB extends StringField(this, 100)
}
object MyPk extends MyPk with BsonMetaRecord[MyPk]
class MyRecord private() extends MongoRecord[MyRecord] {
def meta = MyRecord
object id extends BsonRecordField(this, MyPk) {
override def name = "_id"
}
}
object MyRecord extends MyRecord with MongoMetaRecord[MyRecord]
val pk = MyPk.createRecord.keyA("a").keyB("b")
val rec = MyRecord.createRecord.id(pk).save
Migrating from MongoId to MongoPk
Prior to version 2.4-M1, the id requirement was handled by MongoId. Unfortunately, MongoId defines an _id field and defines the id field as _id.value. In order to migrate MongoRecord.id to type MandatoryTypedField, MongoId has to be removed. For this reason, it has been deprecated.
In order to migrate, you will need to update any references to id to id.is . Any references to _id.is will need to be updated to id.is.
Fields
All standard Record Fields are supported. There is also support for Mongo specific types; ObjectId, UUID, Pattern, List, and Map.
Optional fields
Record Fields have separate Optional implementations. They were added for squeryl-record integration, so Mongo specific fields don’t need them. To make field optional you can override optional_?
field:
class MainDoc private() extends MongoRecord[MainDoc] with ObjectIdPk[MainDoc] {
...
object optional extends BsonRecordField(this, EmbedDoc) {
override def optional_? = true
}
...
}
...
val doc = MainDoc.find(...)
val optional = doc.optional.valueBox
You should use valueBox
to access optional value. Accessing value
field you’ll get back the value in the Box or the defaultValue
:
trait TypedField[ThisType] extends BaseField {
...
def value: MyType = valueBox openOr defaultValue
...
Querying
Record’s Query Methods
All Record’s built-in query methods take either a Mongo DBObject or a Lift JObject as the query parameter. This means you can construct your queries using either the standard mongo-java-driver apis, or Lift’s BsonDSL .
Querying using JObject
Simple queries:
// select * where name = "Joe"
// Mongodb query: db.person.find({ "name" : "Joe"})
import net.liftweb.mongodb.BsonDSL._
Person.findAll(("name" -> "Joe")) // uses the implicit def JsonDSL.pair2jvalue to create a JObject
AND queries using the mongodb implicit AND:
// select * where name = "Joe" AND age = 27
// Mongodb query: db.person.find({ "name" : "Joe", "age" : 27 })
Person.findAll(("name" -> "Joe") ~ ("age" -> 27))
OR queries:
// select * where name = "joe" OR age = 27
// Mongodb query: db.person.find({"$or" : [{"name" : "Joe"}, {"age": 27}] })
Person.findAll("$or" -> List[JObject](("name" -> "Joe"), ("age" -> 27)))
For compound OR queries you need to use the Mongodb explicit AND. The explicit AND is required when the same field or operator has to be specified in multiple expressions:
// select * where (name = "Joe" OR age = 27) AND (city = "London" OR country = "France")
// Mongodb query: db.person.find({"$and": [
// {"$or" : [{"name" : "Joe"}, {"age": 27}] },
// {"$or" : [{"city" : "London"}, {"country": France}] }
// ]})
)
Person.findAll("$and" -> List[JObject] (
("$or" -> List[JObject](("name" -> "Joe"), ("age" -> 27))),
("$or" -> List[JObject](("city" -> "London"), ("country" -> "Paris")))
))
Using Mongodb operators:
// select * where age > 25
Person.find("age" -> ("$gte" -> 25))
Using Javascript:
Person.find("$where" -> ("function() { return this.name=='Joe'}")) // - won't use indexes!
Using Options to conditionally include search clauses …
val age = Some(27)
Person.findAll(("name" -> "Joe") ~ ("age" -> age)) // { "name" : "Joe", "age" : 27 }
If age = None, then Lift removes the field, resulting in a search just on the ‘name’ field.
val age = None
Person.findAll(("name" -> "Joe") ~ ("age" -> age)) // { "name" : "Joe" }
Be warned though, in an OR statement, the result might not be what you expect ..
val age : Option[Int] = None
// { "$or" : [ {"name" : "Joe"}, {} ] } warning - this returns all records !
Person.findAll("$or" -> List[JObject](("name" -> "Joe"), ("age" -> age)))
When appears as an element in a List, Lift’s implicits converts it to a JObject with a single field. If age is Some this would be {"age" : 27 }. However, If age is None, then the field is removed, resulting in the empty object {}. In MongoDB a search containing an empty object matches all records.
Instead, to conditionally include a clause in an OR statement, you could do something like this :
val orClauses : List[JObject] = ("name" -> "Joe") :: age.map( a => ("age" -> a ) : JObject).toList ::: Nil
Person.findAll("$or" -> orClauses)
Some of Lift’s query methods also take other convenient types. E.g
import org.bson.types.ObjectId
// select * where id = "59d9fff4a8c7ef53a3cc36d3"
val id = new ObjectId("59d9fff4a8c7ef53a3cc36d3")
Person.find(id)
Querying using DBObject
import com.mongodb.QueryBuilder
val qry = QueryBuilder.start("name").is("joe")
.put("age").is(27)
.get
Person.findAll(qry)
More Info:
Mongo advanced queries
Rogue
Rogue can be used for querying and updating data:
Rogue is a type-safe internal Scala DSL for constructing and executing find and modify commands against MongoDB in the Lift web framework. It is fully expressive with respect to the basic options provided by MongoDB’s native query language, but in a type-safe manner, building on the record types specified in your Lift models.
Updating
Mongo supports a number of modifier operations including $inc and $set.
To increment two fields using the BasicDBObjectBuilder that is part of the java driver:
import com.mongodb.{BasicDBObject, BasicDBObjectBuilder, DBObject}
val dbo = BasicDBObjectBuilder.start
.append("$inc", BasicDBObjectBuilder.start
.append("cnt", 1)
.append("magic", 42).get).get
MainDoc.update(("name" -> "md1"), dbo)
Or using the BsonDSL:
import net.liftweb.mongodb.BsonDSL._
MainDoc.update(("name" -> "md1"), ("$inc" -> ("cnt" -> 1) ~ ("magic" -> 42))
More Info
Comments are disabled for this space. In order to enable comments, Messages tool must be added to project.
You can add Messages tool from Tools section on the Admin tab.