From 7805a44cdd21e365f64f5c584cc24a65e81612f1 Mon Sep 17 00:00:00 2001 From: Tim Nelson Date: Sat, 1 Oct 2011 08:45:34 -0500 Subject: [PATCH] Issue 1125 - Add MongoRefListField --- .../mongodb/record/MongoMetaRecord.scala | 6 +- .../record/field/BsonRecordField.scala | 2 +- .../mongodb/record/field/MongoListField.scala | 47 ++++++++--- .../mongodb/record/field/MongoRefField.scala | 45 +++------- .../record/field/MongoRefListField.scala | 84 +++++++++++++++++++ .../net/liftweb/mongodb/record/Fixtures.scala | 15 +++- .../mongodb/record/MongoRecordSpec.scala | 11 +++ 7 files changed, 158 insertions(+), 52 deletions(-) create mode 100644 persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefListField.scala diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala index d299ad2ee7..e45f8acebd 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala @@ -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 diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala index e5da28a065..bd6cab443c 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/BsonRecordField.scala @@ -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._ diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala index 3427f58ed5..3a7b1c3f09 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala @@ -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 @@ -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) @@ -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) @@ -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])) } /* @@ -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 = { diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala index e5b4cc0f3d..8aadb8db3f 100644 --- a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefField.scala @@ -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] {} diff --git a/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefListField.scala b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefListField.scala new file mode 100644 index 0000000000..c8abb1e34d --- /dev/null +++ b/persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoRefListField.scala @@ -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) {} diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala index f2e4d74bef..d95083cf64 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala @@ -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 @@ -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") @@ -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 } @@ -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 @@ -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 @@ -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] { diff --git a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala index cd9774ad6c..fca05fcad5 100644 --- a/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala +++ b/persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala @@ -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) } }