Skip to content

Commit

Permalink
Add better support for referenced records in mongodb
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Nelson committed May 3, 2011
1 parent 9646676 commit 2f72d8c
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 18 deletions.
Expand Up @@ -22,6 +22,7 @@ package field
import java.util.Date import java.util.Date


import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
import scala.xml.NodeSeq


import common.{Box, Empty, Failure, Full} import common.{Box, Empty, Failure, Full}
import json.JsonAST._ import json.JsonAST._
Expand Down Expand Up @@ -77,7 +78,7 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType](rec: OwnerTyp
case other => setBox(Failure("Error parsing String into a JValue: "+in)) 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 { def asJValue = JArray(value.map(li => li.asInstanceOf[AnyRef] match {
case x if primitive_?(x.getClass) => primitive2jvalue(x) case x if primitive_?(x.getClass) => primitive2jvalue(x)
Expand Down
@@ -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
}
Expand Up @@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */


package net.liftweb package net.liftweb
package mongodb package mongodb
package record package record
package fixtures package fixtures


import field._ import field._


Expand Down Expand Up @@ -133,7 +133,7 @@ class FieldTypeTestRecord private () extends MongoRecord[FieldTypeTestRecord] wi
object optionalTimeZoneField extends OptionalTimeZoneField(this) object optionalTimeZoneField extends OptionalTimeZoneField(this)


override def equals(other: Any): Boolean = other match { override def equals(other: Any): Boolean = other match {
case that:FieldTypeTestRecord => case that: FieldTypeTestRecord =>
this.id.value == that.id.value && this.id.value == that.id.value &&
//this.mandatoryBinaryField.value == that.mandatoryBinaryField.value && //this.mandatoryBinaryField.value == that.mandatoryBinaryField.value &&
this.mandatoryBooleanField.value == that.mandatoryBooleanField.value && this.mandatoryBooleanField.value == that.mandatoryBooleanField.value &&
Expand Down Expand Up @@ -198,7 +198,7 @@ class MongoFieldTypeTestRecord private () extends MongoRecord[MongoFieldTypeTest
object legacyOptionalUUIDField extends UUIDField(this) { override def optional_? = true } object legacyOptionalUUIDField extends UUIDField(this) { override def optional_? = true }


override def equals(other: Any): Boolean = other match { override def equals(other: Any): Boolean = other match {
case that:MongoFieldTypeTestRecord => case that: MongoFieldTypeTestRecord =>
this.id.value == that.id.value && this.id.value == that.id.value &&
this.mandatoryDateField.value == that.mandatoryDateField.value && this.mandatoryDateField.value == that.mandatoryDateField.value &&
this.mandatoryJsonObjectField.value == that.mandatoryJsonObjectField.value && this.mandatoryJsonObjectField.value == that.mandatoryJsonObjectField.value &&
Expand Down Expand Up @@ -238,7 +238,7 @@ class ListTestRecord private () extends MongoRecord[ListTestRecord] with UUIDPk[
// TODO: More List types // TODO: More List types


override def equals(other: Any): Boolean = other match { override def equals(other: Any): Boolean = other match {
case that:ListTestRecord => case that: ListTestRecord =>
this.id.value == that.id.value && this.id.value == that.id.value &&
this.mandatoryStringListField.value == that.mandatoryStringListField.value && this.mandatoryStringListField.value == that.mandatoryStringListField.value &&
this.mandatoryIntListField.value == that.mandatoryIntListField.value && this.mandatoryIntListField.value == that.mandatoryIntListField.value &&
Expand All @@ -250,7 +250,7 @@ object ListTestRecord extends ListTestRecord with MongoMetaRecord[ListTestRecord
override def formats = allFormats override def formats = allFormats
} }


class MapTestRecord extends MongoRecord[MapTestRecord] with StringPk[MapTestRecord] { class MapTestRecord private () extends MongoRecord[MapTestRecord] with StringPk[MapTestRecord] {
def meta = MapTestRecord def meta = MapTestRecord


object mandatoryStringMapField extends MongoMapField[MapTestRecord, String](this) object mandatoryStringMapField extends MongoMapField[MapTestRecord, String](this)
Expand All @@ -262,7 +262,7 @@ class MapTestRecord extends MongoRecord[MapTestRecord] with StringPk[MapTestReco
// TODO: More Map types, including JsonObject (will require a new Field type) // TODO: More Map types, including JsonObject (will require a new Field type)


override def equals(other: Any): Boolean = other match { override def equals(other: Any): Boolean = other match {
case that:MapTestRecord => case that: MapTestRecord =>
this.id.value == that.id.value && this.id.value == that.id.value &&
this.mandatoryStringMapField.value == that.mandatoryStringMapField.value && this.mandatoryStringMapField.value == that.mandatoryStringMapField.value &&
this.mandatoryIntMapField.value == that.mandatoryIntMapField.value this.mandatoryIntMapField.value == that.mandatoryIntMapField.value
Expand Down Expand Up @@ -290,7 +290,7 @@ object LifecycleTestRecord extends LifecycleTestRecord with MongoMetaRecord[Life
/* /*
* SubRecord fields * SubRecord fields
*/ */
class SubRecord extends BsonRecord[SubRecord] { class SubRecord private () extends BsonRecord[SubRecord] {
def meta = SubRecord def meta = SubRecord


object name extends StringField(this, 12) object name extends StringField(this, 12)
Expand All @@ -304,21 +304,21 @@ class SubRecord extends BsonRecord[SubRecord] {
object uuid extends UUIDField(this) object uuid extends UUIDField(this)


override def equals(other: Any): Boolean = other match { override def equals(other: Any): Boolean = other match {
case that:SubRecord => this.toString == that.toString case that: SubRecord => this.toString == that.toString
case _ => false case _ => false
} }
} }
object SubRecord extends SubRecord with BsonMetaRecord[SubRecord] { object SubRecord extends SubRecord with BsonMetaRecord[SubRecord] {
override def formats = allFormats override def formats = allFormats
} }


class SubSubRecord extends BsonRecord[SubSubRecord] { class SubSubRecord private () extends BsonRecord[SubSubRecord] {
def meta = SubSubRecord def meta = SubSubRecord


object name extends StringField(this, 12) object name extends StringField(this, 12)


override def equals(other: Any): Boolean = other match { override def equals(other: Any): Boolean = other match {
case that:SubSubRecord => case that: SubSubRecord =>
this.name.value == that.name.value this.name.value == that.name.value
case _ => false case _ => false
} }
Expand All @@ -327,7 +327,7 @@ object SubSubRecord extends SubSubRecord with BsonMetaRecord[SubSubRecord] {
override def formats = allFormats override def formats = allFormats
} }


class SubRecordTestRecord extends MongoRecord[SubRecordTestRecord] with ObjectIdPk[SubRecordTestRecord] { class SubRecordTestRecord private () extends MongoRecord[SubRecordTestRecord] with ObjectIdPk[SubRecordTestRecord] {
def meta = SubRecordTestRecord def meta = SubRecordTestRecord


object mandatoryBsonRecordField extends BsonRecordField(this, SubRecord) object mandatoryBsonRecordField extends BsonRecordField(this, SubRecord)
Expand All @@ -341,7 +341,7 @@ class SubRecordTestRecord extends MongoRecord[SubRecordTestRecord] with ObjectId
} }


override def equals(other: Any): Boolean = other match { override def equals(other: Any): Boolean = other match {
case that:SubRecordTestRecord => this.toString == that.toString case that: SubRecordTestRecord => this.toString == that.toString
case _ => false case _ => false
} }


Expand All @@ -355,7 +355,7 @@ case class JsonObj(id: String, name: String) extends JsonObject[JsonObj] {
} }
object JsonObj extends JsonObjectMeta[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 def meta = NullTestRecord


Expand All @@ -367,6 +367,11 @@ class NullTestRecord extends MongoRecord[NullTestRecord] with IntPk[NullTestReco
def defaultValue = JsonObj("1", null) def defaultValue = JsonObj("1", null)
} }
object jsonobjlist extends MongoJsonObjectListField[NullTestRecord, JsonObj](this, JsonObj) 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] object NullTestRecord extends NullTestRecord with MongoMetaRecord[NullTestRecord]
Expand All @@ -377,15 +382,36 @@ extends JsonObject[BoxTestJsonObj] {
} }
object BoxTestJsonObj extends JsonObjectMeta[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 def meta = BoxTestRecord


object jsonobj extends JsonObjectField[BoxTestRecord, BoxTestJsonObj](this, BoxTestJsonObj) { object jsonobj extends JsonObjectField[BoxTestRecord, BoxTestJsonObj](this, BoxTestJsonObj) {
def defaultValue = BoxTestJsonObj("0", Empty, Full("Full String"), Failure("Failure")) def defaultValue = BoxTestJsonObj("0", Empty, Full("Full String"), Failure("Failure"))
} }
object jsonobjlist extends MongoJsonObjectListField[BoxTestRecord, BoxTestJsonObj](this, BoxTestJsonObj) 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] { object BoxTestRecord extends BoxTestRecord with MongoMetaRecord[BoxTestRecord] {
override def formats = super.formats + new JsonBoxSerializer 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
}
Expand Up @@ -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.