Skip to content

Commit

Permalink
Issue 1125 - Add MongoRefListField
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Nelson committed Oct 3, 2011
1 parent f4b8461 commit 7805a44
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 52 deletions.
Expand Up @@ -222,13 +222,15 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]]
/**
* Find all documents with the given ids
*/
def findAll(ids: List[ObjectId]): List[BaseRecord] = if (ids.isEmpty) Nil else {
val list = new java.util.ArrayList[ObjectId]()
def findAllByList[T](ids: List[T]): List[BaseRecord] = if (ids.isEmpty) Nil else {
val list = new java.util.ArrayList[T]()
for (id <- ids.distinct) list.add(id)
val query = QueryBuilder.start("_id").in(list).get()
findAll(query)
}

def findAll(ids: List[ObjectId]): List[BaseRecord] = findAllByList[ObjectId](ids)

protected def saveOp(inst: BaseRecord)(f: => Unit): Boolean = {
foreachCallback(inst, _.beforeSave)
f
Expand Down
Expand Up @@ -76,7 +76,7 @@ class BsonRecordField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonR
* List of BsonRecords
*/
class BsonRecordListField[OwnerType <: BsonRecord[OwnerType], SubRecordType <: BsonRecord[SubRecordType]]
(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])
(rec: OwnerType, valueMeta: BsonMetaRecord[SubRecordType])(implicit mf: Manifest[SubRecordType])
extends MongoListField[OwnerType, SubRecordType](rec: OwnerType) {

import scala.collection.JavaConversions._
Expand Down
Expand Up @@ -27,9 +27,10 @@ import scala.xml.NodeSeq
import common.{Box, Empty, Failure, Full}
import json.JsonAST._
import json.JsonParser
import http.SHtml
import http.js.JE.{JsNull, JsRaw}
import net.liftweb.record.{Field, FieldHelpers, MandatoryTypedField, Record}
import util.Helpers.tryo
import util.Helpers._

import com.mongodb._
import org.bson.types.ObjectId
Expand All @@ -38,24 +39,27 @@ import org.bson.types.ObjectId
* List field. Compatible with most object types,
* including Pattern, ObjectId, Date, and UUID.
*/
class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType](rec: OwnerType)
class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType: Manifest](rec: OwnerType)
extends Field[List[ListType], OwnerType]
with MandatoryTypedField[List[ListType]]
with MongoFieldFlavor[List[ListType]]
{

import Meta.Reflection._

lazy val mf = manifest[ListType]

override type MyType = List[ListType]

def owner = rec

def defaultValue = List[ListType]()

def setFromAny(in: Any): Box[List[ListType]] = {
def setFromAny(in: Any): Box[MyType] = {
in match {
case dbo: DBObject => setFromDBObject(dbo)
case list: List[ListType] => setBox(Full(list))
case Some(list: List[ListType]) => setBox(Full(list))
case Full(list: List[ListType]) => setBox(Full(list))
case list@c::xs if mf.erasure.isInstance(c) => setBox(Full(list.asInstanceOf[MyType]))
case Some(list@c::xs) if mf.erasure.isInstance(c) => setBox(Full(list.asInstanceOf[MyType]))
case Full(list@c::xs) if mf.erasure.isInstance(c) => setBox(Full(list.asInstanceOf[MyType]))
case s: String => setFromString(s)
case Some(s: String) => setFromString(s)
case Full(s: String) => setFromString(s)
Expand All @@ -78,7 +82,28 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType](rec: OwnerTyp
case other => setBox(Failure("Error parsing String into a JValue: "+in))
}

def toForm: Box[NodeSeq] = Empty
/*
* MongoListField is built on MandatoryField, so optional_? is always false. It would be nice to use optional to differentiate
* between a list that requires at least one item and a list that can be empty.
*/

/** Options for select list **/
def options: List[(ListType, String)] = Nil

private def elem = SHtml.multiSelectObj[ListType](
options,
value,
set(_)
) % ("tabindex" -> tabIndex.toString)

def toForm: Box[NodeSeq] =
if (options.length > 0)
uniqueFieldId match {
case Full(id) => Full(elem % ("id" -> id))
case _ => Full(elem)
}
else
Empty

def asJValue = JArray(value.map(li => li.asInstanceOf[AnyRef] match {
case x if primitive_?(x.getClass) => primitive2jvalue(x)
Expand All @@ -105,8 +130,8 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType](rec: OwnerTyp
}

// set this field's value using a DBObject returned from Mongo.
def setFromDBObject(dbo: DBObject): Box[List[ListType]] =
setBox(Full(dbo.asInstanceOf[BasicDBList].toList.asInstanceOf[List[ListType]]))
def setFromDBObject(dbo: DBObject): Box[MyType] =
setBox(Full(dbo.asInstanceOf[BasicDBList].toList.asInstanceOf[MyType]))
}

/*
Expand All @@ -121,7 +146,7 @@ class MongoDateListField[OwnerType <: BsonRecord[OwnerType]](rec: OwnerType)
* List of JsonObject case classes
*/
class MongoJsonObjectListField[OwnerType <: BsonRecord[OwnerType], JObjectType <: JsonObject[JObjectType]]
(rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType])
(rec: OwnerType, valueMeta: JsonObjectMeta[JObjectType])(implicit mf: Manifest[JObjectType])
extends MongoListField[OwnerType, JObjectType](rec: OwnerType) {

override def asDBObject: DBObject = {
Expand Down
Expand Up @@ -90,46 +90,21 @@ trait MongoRefField[RefType <: MongoRecord[RefType], MyType] extends TypedField[
}

class ObjectIdRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
rec: OwnerType, rm: MongoMetaRecord[RefType]
)
extends ObjectIdField[OwnerType](rec)
with MongoRefField[RefType, ObjectId]
{
def refMeta = rm
}
rec: OwnerType, val refMeta: MongoMetaRecord[RefType]
) extends ObjectIdField[OwnerType](rec) with MongoRefField[RefType, ObjectId] {}

class UUIDRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
rec: OwnerType, rm: MongoMetaRecord[RefType]
)
extends UUIDField[OwnerType](rec)
with MongoRefField[RefType, UUID]
{
def refMeta = rm
}
rec: OwnerType, val refMeta: MongoMetaRecord[RefType]
) extends UUIDField[OwnerType](rec) with MongoRefField[RefType, UUID] {}

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
}
rec: OwnerType, val refMeta: MongoMetaRecord[RefType], maxLen: Int
) extends StringField[OwnerType](rec, maxLen) with MongoRefField[RefType, String] {}

class IntRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
rec: OwnerType, rm: MongoMetaRecord[RefType]
)
extends IntField[OwnerType](rec)
with MongoRefField[RefType, Int]
{
def refMeta = rm
}
rec: OwnerType, val refMeta: MongoMetaRecord[RefType]
) extends IntField[OwnerType](rec) with MongoRefField[RefType, Int] {}

class LongRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
rec: OwnerType, rm: MongoMetaRecord[RefType]
)
extends LongField[OwnerType](rec)
with MongoRefField[RefType, Long]
{
def refMeta = rm
}
rec: OwnerType, val refMeta: MongoMetaRecord[RefType]
) extends LongField[OwnerType](rec) with MongoRefField[RefType, Long] {}
@@ -0,0 +1,84 @@
/*
* 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._
import http.{S, SHtml}
import net.liftweb.record.{Field, MandatoryTypedField, TypedField}

import java.util.UUID

import org.bson.types.ObjectId

/*
* Trait for creating a Field for storing a list of "foreign keys". Caches the
* items after fetching. Implementations are available for ObjectId, UUID, String,
* Int, and Long, but you can extend this.
*
* toForm produces a multi-select form element. You just need to supply the
* options by overriding the options method.
*/
abstract class MongoRefListField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType], MyType]
(rec: OwnerType)(implicit mf: Manifest[MyType]) extends MongoListField[OwnerType, MyType](rec) {

/** The MongoMetaRecord of the referenced object **/
def refMeta: MongoMetaRecord[RefType]

/*
* get the referenced objects
*/
def objs = synchronized {
if (!_calcedObjs) {
_calcedObjs = true
this._objs = refMeta.findAllByList(this.value)
}
_objs
}

def cached_? : Boolean = synchronized { _calcedObjs }

def primeObjs(objs: List[RefType]) = synchronized {
_objs = objs
_calcedObjs = true
}

private var _objs: List[RefType] = Nil
private var _calcedObjs = false
}

class ObjectIdRefListField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
rec: OwnerType, val refMeta: MongoMetaRecord[RefType]
) extends MongoRefListField[OwnerType, RefType, ObjectId](rec) {}

class UUIDRefListField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
rec: OwnerType, val refMeta: MongoMetaRecord[RefType]
) extends MongoRefListField[OwnerType, RefType, UUID](rec) {}

class StringRefListField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
rec: OwnerType, val refMeta: MongoMetaRecord[RefType]
) extends MongoRefListField[OwnerType, RefType, String](rec) {}

class IntRefListField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
rec: OwnerType, val refMeta: MongoMetaRecord[RefType]
) extends MongoRefListField[OwnerType, RefType, Int](rec) {}

class LongRefListField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
rec: OwnerType, val refMeta: MongoMetaRecord[RefType]
) extends MongoRefListField[OwnerType, RefType, Long](rec) {}
Expand Up @@ -23,6 +23,7 @@ import field._

import common.{Box, Empty, Failure, Full}
import json.ext.JsonBoxSerializer
import http.SHtml
import util.FieldError

import java.math.MathContext
Expand All @@ -31,6 +32,8 @@ import scala.xml.Text
import net.liftweb.record._
import net.liftweb.record.field._

import org.bson.types.ObjectId

object MyTestEnum extends Enumeration {
val ONE = Value("ONE")
val TWO = Value("TWO")
Expand Down Expand Up @@ -156,7 +159,7 @@ class BinaryFieldTestRecord extends MongoRecord[BinaryFieldTestRecord] with IntP
object mandatoryBinaryField extends BinaryField(this) {
// compare the elements of the Array
override def equals(other: Any): Boolean = other match {
case that: BinaryField[Any] =>
case that: BinaryField[_] =>
this.value.zip(that.value).filter(t => t._1 != t._2).length == 0
case _ => false
}
Expand All @@ -165,7 +168,7 @@ class BinaryFieldTestRecord extends MongoRecord[BinaryFieldTestRecord] with IntP
override def optional_? = true
// compare the elements of the Array
override def equals(other: Any): Boolean = other match {
case that: BinaryField[Any] => (this.valueBox, that.valueBox) match {
case that: BinaryField[_] => (this.valueBox, that.valueBox) match {
case (Empty, Empty) => true
case (Full(a), Full(b)) =>
a.zip(b).filter(t => t._1 != t._2).length == 0
Expand All @@ -177,7 +180,7 @@ class BinaryFieldTestRecord extends MongoRecord[BinaryFieldTestRecord] with IntP
object optionalBinaryField extends OptionalBinaryField(this) {
// compare the elements of the Array
override def equals(other: Any): Boolean = other match {
case that: OptionalBinaryField[Any] => (this.valueBox, that.valueBox) match {
case that: OptionalBinaryField[_] => (this.valueBox, that.valueBox) match {
case (Empty, Empty) => true
case (Full(a), Full(b)) =>
a.zip(b).filter(t => t._1 != t._2).length == 0
Expand Down Expand Up @@ -453,6 +456,12 @@ class RefFieldTestRecord private () extends MongoRecord[RefFieldTestRecord] with
object mandatoryStringRefField extends StringRefField(this, MapTestRecord, 100)
object mandatoryIntRefField extends IntRefField(this, NullTestRecord)
object mandatoryLongRefField extends LongRefField(this, BoxTestRecord)

object mandatoryObjectIdRefListField extends ObjectIdRefListField(this, FieldTypeTestRecord)
object mandatoryUUIDRefListField extends UUIDRefListField(this, ListTestRecord)
object mandatoryStringRefListField extends StringRefListField(this, MapTestRecord)
object mandatoryIntRefListField extends IntRefListField(this, NullTestRecord)
object mandatoryLongRefListField extends LongRefListField(this, BoxTestRecord)
}

object RefFieldTestRecord extends RefFieldTestRecord with MongoMetaRecord[RefFieldTestRecord] {
Expand Down
Expand Up @@ -541,12 +541,23 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M
.mandatoryStringRefField(mtr.id.is)
.mandatoryIntRefField(ntr.id.is)
.mandatoryLongRefField(btr.id.is)
.mandatoryObjectIdRefListField(List(fttr.id.is))
.mandatoryUUIDRefListField(List(ltr.id.is))
.mandatoryStringRefListField(List(mtr.id.is))
.mandatoryIntRefListField(List(ntr.id.is))
.mandatoryLongRefListField(List(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)

rftr.mandatoryObjectIdRefListField.objs mustEqual List(fttr)
rftr.mandatoryUUIDRefListField.objs mustEqual List(ltr)
rftr.mandatoryStringRefListField.objs mustEqual List(mtr)
rftr.mandatoryIntRefListField.objs mustEqual List(ntr)
rftr.mandatoryLongRefListField.objs mustEqual List(btr)
}

}
Expand Down

0 comments on commit 7805a44

Please sign in to comment.