Permalink
Browse files

Add better support for referenced records in mongodb

  • Loading branch information...
1 parent 9646676 commit 2f72d8cf7efadbafb1656ad6bb02fd896cdd1a46 @eltimn eltimn committed Mar 10, 2011
@@ -22,6 +22,7 @@ package field
import java.util.Date
import scala.collection.JavaConversions._
+import scala.xml.NodeSeq
import common.{Box, Empty, Failure, Full}
import json.JsonAST._
@@ -77,7 +78,7 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType](rec: OwnerTyp
case other => setBox(Failure("Error parsing String into a JValue: "+in))
}
- def toForm = Empty // FIXME
+ def toForm: Box[NodeSeq] = Empty
def asJValue = JArray(value.map(li => li.asInstanceOf[AnyRef] match {
case x if primitive_?(x.getClass) => primitive2jvalue(x)
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package mongodb
+package record
+package field
+
+import common.{Box, Empty, Full}
+import http.SHtml
+import util.Helpers._
+
+import java.util.UUID
+
+import org.bson.types.ObjectId
+import net.liftweb.record.TypedField
+import net.liftweb.record.field._
+
+/*
+ * Trait for creating a Field for storing a "foreign key". Caches the
+ * item after fetching. Implementations are available for ObjectId, UUID, String,
+ * Int, and Long, but you can mix this into any Field.
+ *
+ * toForm produces a select form element. You just need to supply the
+ * options by overriding the options method.
+ */
+trait MongoRefField[RefType <: MongoRecord[RefType], MyType] extends TypedField[MyType] {
+
+ /** The MongoMetaRecord of the referenced object **/
+ def refMeta: MongoMetaRecord[RefType]
+
+ /*
+ * get the referenced object
+ */
+ def obj = synchronized {
+ if (!_calcedObj) {
+ _calcedObj = true
+ this._obj = valueBox.flatMap(v => refMeta.findAny(v))
+ }
+ _obj
+ }
+
+ def cached_? : Boolean = synchronized { _calcedObj }
+
+ def primeObj(obj: Box[RefType]) = synchronized {
+ _obj = obj
+ _calcedObj = true
+ }
+
+ private var _obj: Box[RefType] = Empty
+ private var _calcedObj = false
+
+ /** Options for select list **/
+ def options: List[(Box[MyType], String)] = Nil
+
+ /** Label for the selection item representing Empty, show when this field is optional. Defaults to the empty string. */
+ def emptyOptionLabel: String = ""
+
+ def buildDisplayList: List[(Box[MyType], String)] = {
+ if (optional_?) (Empty, emptyOptionLabel)::options else options
+ }
+
+ private def elem = SHtml.selectObj[Box[MyType]](
+ buildDisplayList,
+ Full(valueBox),
+ setBox(_)
+ ) % ("tabindex" -> tabIndex.toString)
+
+ override def toForm =
+ if (options.length > 0)
+ uniqueFieldId match {
+ case Full(id) => Full(elem % ("id" -> id))
+ case _ => Full(elem)
+ }
+ else
+ Empty
+}
+
+class ObjectIdRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
+ rec: OwnerType, rm: MongoMetaRecord[RefType]
+)
+ extends ObjectIdField[OwnerType](rec)
+ with MongoRefField[RefType, ObjectId]
+{
+ def refMeta = rm
+}
+
+class UUIDRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
+ rec: OwnerType, rm: MongoMetaRecord[RefType]
+)
+ extends UUIDField[OwnerType](rec)
+ with MongoRefField[RefType, UUID]
+{
+ def refMeta = rm
+}
+
+class StringRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
+ rec: OwnerType, rm: MongoMetaRecord[RefType], maxLen: Int
+)
+ extends StringField[OwnerType](rec, maxLen)
+ with MongoRefField[RefType, String]
+{
+ def refMeta = rm
+}
+
+class IntRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
+ rec: OwnerType, rm: MongoMetaRecord[RefType]
+)
+ extends IntField[OwnerType](rec)
+ with MongoRefField[RefType, Int]
+{
+ def refMeta = rm
+}
+
+class LongRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
+ rec: OwnerType, rm: MongoMetaRecord[RefType]
+)
+ extends LongField[OwnerType](rec)
+ with MongoRefField[RefType, Long]
+{
+ def refMeta = rm
+}
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package net.liftweb
-package mongodb
-package record
-package fixtures
+package net.liftweb
+package mongodb
+package record
+package fixtures
import field._
@@ -133,7 +133,7 @@ class FieldTypeTestRecord private () extends MongoRecord[FieldTypeTestRecord] wi
object optionalTimeZoneField extends OptionalTimeZoneField(this)
override def equals(other: Any): Boolean = other match {
- case that:FieldTypeTestRecord =>
+ case that: FieldTypeTestRecord =>
this.id.value == that.id.value &&
//this.mandatoryBinaryField.value == that.mandatoryBinaryField.value &&
this.mandatoryBooleanField.value == that.mandatoryBooleanField.value &&
@@ -198,7 +198,7 @@ class MongoFieldTypeTestRecord private () extends MongoRecord[MongoFieldTypeTest
object legacyOptionalUUIDField extends UUIDField(this) { override def optional_? = true }
override def equals(other: Any): Boolean = other match {
- case that:MongoFieldTypeTestRecord =>
+ case that: MongoFieldTypeTestRecord =>
this.id.value == that.id.value &&
this.mandatoryDateField.value == that.mandatoryDateField.value &&
this.mandatoryJsonObjectField.value == that.mandatoryJsonObjectField.value &&
@@ -238,7 +238,7 @@ class ListTestRecord private () extends MongoRecord[ListTestRecord] with UUIDPk[
// TODO: More List types
override def equals(other: Any): Boolean = other match {
- case that:ListTestRecord =>
+ case that: ListTestRecord =>
this.id.value == that.id.value &&
this.mandatoryStringListField.value == that.mandatoryStringListField.value &&
this.mandatoryIntListField.value == that.mandatoryIntListField.value &&
@@ -250,7 +250,7 @@ object ListTestRecord extends ListTestRecord with MongoMetaRecord[ListTestRecord
override def formats = allFormats
}
-class MapTestRecord extends MongoRecord[MapTestRecord] with StringPk[MapTestRecord] {
+class MapTestRecord private () extends MongoRecord[MapTestRecord] with StringPk[MapTestRecord] {
def meta = MapTestRecord
object mandatoryStringMapField extends MongoMapField[MapTestRecord, String](this)
@@ -262,7 +262,7 @@ class MapTestRecord extends MongoRecord[MapTestRecord] with StringPk[MapTestReco
// TODO: More Map types, including JsonObject (will require a new Field type)
override def equals(other: Any): Boolean = other match {
- case that:MapTestRecord =>
+ case that: MapTestRecord =>
this.id.value == that.id.value &&
this.mandatoryStringMapField.value == that.mandatoryStringMapField.value &&
this.mandatoryIntMapField.value == that.mandatoryIntMapField.value
@@ -290,7 +290,7 @@ object LifecycleTestRecord extends LifecycleTestRecord with MongoMetaRecord[Life
/*
* SubRecord fields
*/
-class SubRecord extends BsonRecord[SubRecord] {
+class SubRecord private () extends BsonRecord[SubRecord] {
def meta = SubRecord
object name extends StringField(this, 12)
@@ -304,21 +304,21 @@ class SubRecord extends BsonRecord[SubRecord] {
object uuid extends UUIDField(this)
override def equals(other: Any): Boolean = other match {
- case that:SubRecord => this.toString == that.toString
+ case that: SubRecord => this.toString == that.toString
case _ => false
}
}
object SubRecord extends SubRecord with BsonMetaRecord[SubRecord] {
override def formats = allFormats
}
-class SubSubRecord extends BsonRecord[SubSubRecord] {
+class SubSubRecord private () extends BsonRecord[SubSubRecord] {
def meta = SubSubRecord
object name extends StringField(this, 12)
override def equals(other: Any): Boolean = other match {
- case that:SubSubRecord =>
+ case that: SubSubRecord =>
this.name.value == that.name.value
case _ => false
}
@@ -327,7 +327,7 @@ object SubSubRecord extends SubSubRecord with BsonMetaRecord[SubSubRecord] {
override def formats = allFormats
}
-class SubRecordTestRecord extends MongoRecord[SubRecordTestRecord] with ObjectIdPk[SubRecordTestRecord] {
+class SubRecordTestRecord private () extends MongoRecord[SubRecordTestRecord] with ObjectIdPk[SubRecordTestRecord] {
def meta = SubRecordTestRecord
object mandatoryBsonRecordField extends BsonRecordField(this, SubRecord)
@@ -341,7 +341,7 @@ class SubRecordTestRecord extends MongoRecord[SubRecordTestRecord] with ObjectId
}
override def equals(other: Any): Boolean = other match {
- case that:SubRecordTestRecord => this.toString == that.toString
+ case that: SubRecordTestRecord => this.toString == that.toString
case _ => false
}
@@ -355,7 +355,7 @@ case class JsonObj(id: String, name: String) extends JsonObject[JsonObj] {
}
object JsonObj extends JsonObjectMeta[JsonObj]
-class NullTestRecord extends MongoRecord[NullTestRecord] with IntPk[NullTestRecord] {
+class NullTestRecord private () extends MongoRecord[NullTestRecord] with IntPk[NullTestRecord] {
def meta = NullTestRecord
@@ -367,6 +367,11 @@ class NullTestRecord extends MongoRecord[NullTestRecord] with IntPk[NullTestReco
def defaultValue = JsonObj("1", null)
}
object jsonobjlist extends MongoJsonObjectListField[NullTestRecord, JsonObj](this, JsonObj)
+
+ override def equals(other: Any): Boolean = other match {
+ case that: NullTestRecord => this.toString == that.toString
+ case _ => false
+ }
}
object NullTestRecord extends NullTestRecord with MongoMetaRecord[NullTestRecord]
@@ -377,15 +382,36 @@ extends JsonObject[BoxTestJsonObj] {
}
object BoxTestJsonObj extends JsonObjectMeta[BoxTestJsonObj]
-class BoxTestRecord extends MongoRecord[BoxTestRecord] with LongPk[BoxTestRecord] {
+class BoxTestRecord private () extends MongoRecord[BoxTestRecord] with LongPk[BoxTestRecord] {
def meta = BoxTestRecord
object jsonobj extends JsonObjectField[BoxTestRecord, BoxTestJsonObj](this, BoxTestJsonObj) {
def defaultValue = BoxTestJsonObj("0", Empty, Full("Full String"), Failure("Failure"))
}
object jsonobjlist extends MongoJsonObjectListField[BoxTestRecord, BoxTestJsonObj](this, BoxTestJsonObj)
+
+ override def equals(other: Any): Boolean = other match {
+ case that: BoxTestRecord => this.toString == that.toString
+ case _ => false
+ }
}
object BoxTestRecord extends BoxTestRecord with MongoMetaRecord[BoxTestRecord] {
override def formats = super.formats + new JsonBoxSerializer
}
+/*
+ * MongoRefFields
+ */
+class RefFieldTestRecord private () extends MongoRecord[RefFieldTestRecord] with ObjectIdPk[RefFieldTestRecord] {
+ def meta = RefFieldTestRecord
+
+ object mandatoryObjectIdRefField extends ObjectIdRefField(this, FieldTypeTestRecord)
+ object mandatoryUUIDRefField extends UUIDRefField(this, ListTestRecord)
+ object mandatoryStringRefField extends StringRefField(this, MapTestRecord, 100)
+ object mandatoryIntRefField extends IntRefField(this, NullTestRecord)
+ object mandatoryLongRefField extends LongRefField(this, BoxTestRecord)
+}
+
+object RefFieldTestRecord extends RefFieldTestRecord with MongoMetaRecord[RefFieldTestRecord] {
+ override def formats = allFormats
+}
@@ -511,6 +511,32 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M
}
}
+ "retrieve MongoRef objects properly" in {
+ checkMongoIsRunning
+
+ val ntr = NullTestRecord.createRecord
+ val btr = BoxTestRecord.createRecord
+
+ fttr.save
+ ltr.save
+ mtr.save
+ ntr.save
+ btr.save
+
+ val rftr = RefFieldTestRecord.createRecord
+ .mandatoryObjectIdRefField(fttr.id.is)
+ .mandatoryUUIDRefField(ltr.id.is)
+ .mandatoryStringRefField(mtr.id.is)
+ .mandatoryIntRefField(ntr.id.is)
+ .mandatoryLongRefField(btr.id.is)
+
+ rftr.mandatoryObjectIdRefField.obj mustEqual Full(fttr)
+ rftr.mandatoryUUIDRefField.obj mustEqual Full(ltr)
+ rftr.mandatoryStringRefField.obj mustEqual Full(mtr)
+ rftr.mandatoryIntRefField.obj mustEqual Full(ntr)
+ rftr.mandatoryLongRefField.obj mustEqual Full(btr)
+ }
+
}
}

0 comments on commit 2f72d8c

Please sign in to comment.