When working with document-based databases such as MongoDB, instead of using a one-to-many relationship to link to classes that represent two tables in the database—such as contacts
and phones
—we can map both objects Contact
and Phone
. Then, instead of storing them in two different collections, we embed the phone
object into the contact
object. In the following recipe, we will learn how to embed one object into another.
Carry out the following steps:
Phone.scala
in src/main/scala/code/model
with the following code:package code.model import net.liftweb.mongodb.record._ import net.liftweb.record.field.StringField class Phone extends BsonRecord[Phone] { def meta = Phone val number = new StringField(this, 15) } object Phone extends Phone with BsonMetaRecord[Phone]
Contact
class by adding the numbers
object as follows:class Contact extends MongoRecord[Contact] with ObjectIdPk[Contact] { def meta = Contact object name extends StringField(this, 150) object numbers extends BsonRecordListField(this, Phone) }
Contacts
snippet as follows:class Contacts { private def createContact(name: String, phoneNumbers: List[String]) { val phones = phoneNumbers.map(Phone.createRecord.number(_)) Contact.createRecord.name(name).numbers(phones).save } def prepareContacts_!() { Contact.findAll.map(_.delete_!) createContact("John", "555-5555" :: Nil) createContact("Joe", "444-4444" :: "222-2222" :: Nil) createContact("Lisa", "333-3333" :: Nil) } def list = { prepareContacts_!() "li *" #> Contact.findAll.map { contact => { ".name *" #> contact.name.get & ".phone *" #> contact.numbers.get.map(_.number.get) } } } }
ul
tag in the index.html
file as follows:<ul> <li> <span class="name"></span> <ul> <li class="phone"></li> </ul> </li> </ul>
http://localhost:8080
and you'll see a web page such as the one shown in the following screenshot:The first thing to notice is that while the Contact
class extends MongoRecord
, the Phone
class extends BsonRecord
, which is a special type of Record
that is used to encode and decode BSON/DBObject objects. This means that Lift will know how to convert the BSON representation of a phone number—which is the format that MongoDB uses to save the data—into a DBObject, which is a key-value map that can be saved in the database.
One important difference between the MongoRecord
and BsonRecord
traits is that the latter can't save itself, while the former can. The consequence of this difference is that we can't invoke the save
or saveTheRecord
methods of a BsonRecord
object. Thus, the only thing we can do is to embed it in a MongoRecord
object.
Another difference between the Phone
and Contact
classes is their companion object. In the Contact
class, we extended MongoRecord
, and its companion object extended the MongoMetaRecord
trait. In the Phone
class, we extended BsonRecord
, and its companion object extended the BsonMetaRecord
trait. As you can imagine, MongoMetaRecord
is used to deal with MongoRecord
objects, and BsonMetaRecord
is used to deal with BsonRecord
objects.
So, if the only thing we can do with BsonRecord
is to embed it into MongoRecord
, how do we do it?
If you take a look at the Contact
class, you'll see that the numbers
object we added extends the BsonRecordListField
class, which can hold a list of BsonRecord
objects.
Note that the BsonRecordListField
class takes two arguments, the first one being the owner—that's why we passed the value this
—and the second argument being the object we want to embed, in this case, the Phone
object's BsonMetaRecord
trait.
We have embeded a list of Phone
objects into the Contact
class. But, what if you just want to embed one object instead of a list? In this case, you can use the BsonRecordField
class.
The difference between the BsonRecordListField
and BsonRecordField
classes is that the latter contains only one object, while the former can contain one or more objects. This means that when saving the data into MongoDB, the BsonRecordField
class will be saved as a subobject and BsonRecordListField
will be saved as an array of objects.
18.117.103.5