Mongo Record Basics

Version 14, last updated by damianhelme at 2017-10-20

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

Other Record Query Methods

BasicDBObjectBuilder

Mongo update


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.