Skip to content

Commit 2f72d8c

Browse files
committed
Add better support for referenced records in mongodb
1 parent 9646676 commit 2f72d8c

File tree

4 files changed

+206
-18
lines changed

4 files changed

+206
-18
lines changed

persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/field/MongoListField.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package field
2222
import java.util.Date
2323

2424
import scala.collection.JavaConversions._
25+
import scala.xml.NodeSeq
2526

2627
import common.{Box, Empty, Failure, Full}
2728
import json.JsonAST._
@@ -77,7 +78,7 @@ class MongoListField[OwnerType <: BsonRecord[OwnerType], ListType](rec: OwnerTyp
7778
case other => setBox(Failure("Error parsing String into a JValue: "+in))
7879
}
7980

80-
def toForm = Empty // FIXME
81+
def toForm: Box[NodeSeq] = Empty
8182

8283
def asJValue = JArray(value.map(li => li.asInstanceOf[AnyRef] match {
8384
case x if primitive_?(x.getClass) => primitive2jvalue(x)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2011 WorldWide Conferencing, LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package net.liftweb
18+
package mongodb
19+
package record
20+
package field
21+
22+
import common.{Box, Empty, Full}
23+
import http.SHtml
24+
import util.Helpers._
25+
26+
import java.util.UUID
27+
28+
import org.bson.types.ObjectId
29+
import net.liftweb.record.TypedField
30+
import net.liftweb.record.field._
31+
32+
/*
33+
* Trait for creating a Field for storing a "foreign key". Caches the
34+
* item after fetching. Implementations are available for ObjectId, UUID, String,
35+
* Int, and Long, but you can mix this into any Field.
36+
*
37+
* toForm produces a select form element. You just need to supply the
38+
* options by overriding the options method.
39+
*/
40+
trait MongoRefField[RefType <: MongoRecord[RefType], MyType] extends TypedField[MyType] {
41+
42+
/** The MongoMetaRecord of the referenced object **/
43+
def refMeta: MongoMetaRecord[RefType]
44+
45+
/*
46+
* get the referenced object
47+
*/
48+
def obj = synchronized {
49+
if (!_calcedObj) {
50+
_calcedObj = true
51+
this._obj = valueBox.flatMap(v => refMeta.findAny(v))
52+
}
53+
_obj
54+
}
55+
56+
def cached_? : Boolean = synchronized { _calcedObj }
57+
58+
def primeObj(obj: Box[RefType]) = synchronized {
59+
_obj = obj
60+
_calcedObj = true
61+
}
62+
63+
private var _obj: Box[RefType] = Empty
64+
private var _calcedObj = false
65+
66+
/** Options for select list **/
67+
def options: List[(Box[MyType], String)] = Nil
68+
69+
/** Label for the selection item representing Empty, show when this field is optional. Defaults to the empty string. */
70+
def emptyOptionLabel: String = ""
71+
72+
def buildDisplayList: List[(Box[MyType], String)] = {
73+
if (optional_?) (Empty, emptyOptionLabel)::options else options
74+
}
75+
76+
private def elem = SHtml.selectObj[Box[MyType]](
77+
buildDisplayList,
78+
Full(valueBox),
79+
setBox(_)
80+
) % ("tabindex" -> tabIndex.toString)
81+
82+
override def toForm =
83+
if (options.length > 0)
84+
uniqueFieldId match {
85+
case Full(id) => Full(elem % ("id" -> id))
86+
case _ => Full(elem)
87+
}
88+
else
89+
Empty
90+
}
91+
92+
class ObjectIdRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
93+
rec: OwnerType, rm: MongoMetaRecord[RefType]
94+
)
95+
extends ObjectIdField[OwnerType](rec)
96+
with MongoRefField[RefType, ObjectId]
97+
{
98+
def refMeta = rm
99+
}
100+
101+
class UUIDRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
102+
rec: OwnerType, rm: MongoMetaRecord[RefType]
103+
)
104+
extends UUIDField[OwnerType](rec)
105+
with MongoRefField[RefType, UUID]
106+
{
107+
def refMeta = rm
108+
}
109+
110+
class StringRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
111+
rec: OwnerType, rm: MongoMetaRecord[RefType], maxLen: Int
112+
)
113+
extends StringField[OwnerType](rec, maxLen)
114+
with MongoRefField[RefType, String]
115+
{
116+
def refMeta = rm
117+
}
118+
119+
class IntRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
120+
rec: OwnerType, rm: MongoMetaRecord[RefType]
121+
)
122+
extends IntField[OwnerType](rec)
123+
with MongoRefField[RefType, Int]
124+
{
125+
def refMeta = rm
126+
}
127+
128+
class LongRefField[OwnerType <: BsonRecord[OwnerType], RefType <: MongoRecord[RefType]](
129+
rec: OwnerType, rm: MongoMetaRecord[RefType]
130+
)
131+
extends LongField[OwnerType](rec)
132+
with MongoRefField[RefType, Long]
133+
{
134+
def refMeta = rm
135+
}

persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala

+43-17
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17-
package net.liftweb
18-
package mongodb
19-
package record
20-
package fixtures
17+
package net.liftweb
18+
package mongodb
19+
package record
20+
package fixtures
2121

2222
import field._
2323

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

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

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

240240
override def equals(other: Any): Boolean = other match {
241-
case that:ListTestRecord =>
241+
case that: ListTestRecord =>
242242
this.id.value == that.id.value &&
243243
this.mandatoryStringListField.value == that.mandatoryStringListField.value &&
244244
this.mandatoryIntListField.value == that.mandatoryIntListField.value &&
@@ -250,7 +250,7 @@ object ListTestRecord extends ListTestRecord with MongoMetaRecord[ListTestRecord
250250
override def formats = allFormats
251251
}
252252

253-
class MapTestRecord extends MongoRecord[MapTestRecord] with StringPk[MapTestRecord] {
253+
class MapTestRecord private () extends MongoRecord[MapTestRecord] with StringPk[MapTestRecord] {
254254
def meta = MapTestRecord
255255

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

264264
override def equals(other: Any): Boolean = other match {
265-
case that:MapTestRecord =>
265+
case that: MapTestRecord =>
266266
this.id.value == that.id.value &&
267267
this.mandatoryStringMapField.value == that.mandatoryStringMapField.value &&
268268
this.mandatoryIntMapField.value == that.mandatoryIntMapField.value
@@ -290,7 +290,7 @@ object LifecycleTestRecord extends LifecycleTestRecord with MongoMetaRecord[Life
290290
/*
291291
* SubRecord fields
292292
*/
293-
class SubRecord extends BsonRecord[SubRecord] {
293+
class SubRecord private () extends BsonRecord[SubRecord] {
294294
def meta = SubRecord
295295

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

306306
override def equals(other: Any): Boolean = other match {
307-
case that:SubRecord => this.toString == that.toString
307+
case that: SubRecord => this.toString == that.toString
308308
case _ => false
309309
}
310310
}
311311
object SubRecord extends SubRecord with BsonMetaRecord[SubRecord] {
312312
override def formats = allFormats
313313
}
314314

315-
class SubSubRecord extends BsonRecord[SubSubRecord] {
315+
class SubSubRecord private () extends BsonRecord[SubSubRecord] {
316316
def meta = SubSubRecord
317317

318318
object name extends StringField(this, 12)
319319

320320
override def equals(other: Any): Boolean = other match {
321-
case that:SubSubRecord =>
321+
case that: SubSubRecord =>
322322
this.name.value == that.name.value
323323
case _ => false
324324
}
@@ -327,7 +327,7 @@ object SubSubRecord extends SubSubRecord with BsonMetaRecord[SubSubRecord] {
327327
override def formats = allFormats
328328
}
329329

330-
class SubRecordTestRecord extends MongoRecord[SubRecordTestRecord] with ObjectIdPk[SubRecordTestRecord] {
330+
class SubRecordTestRecord private () extends MongoRecord[SubRecordTestRecord] with ObjectIdPk[SubRecordTestRecord] {
331331
def meta = SubRecordTestRecord
332332

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

343343
override def equals(other: Any): Boolean = other match {
344-
case that:SubRecordTestRecord => this.toString == that.toString
344+
case that: SubRecordTestRecord => this.toString == that.toString
345345
case _ => false
346346
}
347347

@@ -355,7 +355,7 @@ case class JsonObj(id: String, name: String) extends JsonObject[JsonObj] {
355355
}
356356
object JsonObj extends JsonObjectMeta[JsonObj]
357357

358-
class NullTestRecord extends MongoRecord[NullTestRecord] with IntPk[NullTestRecord] {
358+
class NullTestRecord private () extends MongoRecord[NullTestRecord] with IntPk[NullTestRecord] {
359359

360360
def meta = NullTestRecord
361361

@@ -367,6 +367,11 @@ class NullTestRecord extends MongoRecord[NullTestRecord] with IntPk[NullTestReco
367367
def defaultValue = JsonObj("1", null)
368368
}
369369
object jsonobjlist extends MongoJsonObjectListField[NullTestRecord, JsonObj](this, JsonObj)
370+
371+
override def equals(other: Any): Boolean = other match {
372+
case that: NullTestRecord => this.toString == that.toString
373+
case _ => false
374+
}
370375
}
371376

372377
object NullTestRecord extends NullTestRecord with MongoMetaRecord[NullTestRecord]
@@ -377,15 +382,36 @@ extends JsonObject[BoxTestJsonObj] {
377382
}
378383
object BoxTestJsonObj extends JsonObjectMeta[BoxTestJsonObj]
379384

380-
class BoxTestRecord extends MongoRecord[BoxTestRecord] with LongPk[BoxTestRecord] {
385+
class BoxTestRecord private () extends MongoRecord[BoxTestRecord] with LongPk[BoxTestRecord] {
381386
def meta = BoxTestRecord
382387

383388
object jsonobj extends JsonObjectField[BoxTestRecord, BoxTestJsonObj](this, BoxTestJsonObj) {
384389
def defaultValue = BoxTestJsonObj("0", Empty, Full("Full String"), Failure("Failure"))
385390
}
386391
object jsonobjlist extends MongoJsonObjectListField[BoxTestRecord, BoxTestJsonObj](this, BoxTestJsonObj)
392+
393+
override def equals(other: Any): Boolean = other match {
394+
case that: BoxTestRecord => this.toString == that.toString
395+
case _ => false
396+
}
387397
}
388398
object BoxTestRecord extends BoxTestRecord with MongoMetaRecord[BoxTestRecord] {
389399
override def formats = super.formats + new JsonBoxSerializer
390400
}
391401

402+
/*
403+
* MongoRefFields
404+
*/
405+
class RefFieldTestRecord private () extends MongoRecord[RefFieldTestRecord] with ObjectIdPk[RefFieldTestRecord] {
406+
def meta = RefFieldTestRecord
407+
408+
object mandatoryObjectIdRefField extends ObjectIdRefField(this, FieldTypeTestRecord)
409+
object mandatoryUUIDRefField extends UUIDRefField(this, ListTestRecord)
410+
object mandatoryStringRefField extends StringRefField(this, MapTestRecord, 100)
411+
object mandatoryIntRefField extends IntRefField(this, NullTestRecord)
412+
object mandatoryLongRefField extends LongRefField(this, BoxTestRecord)
413+
}
414+
415+
object RefFieldTestRecord extends RefFieldTestRecord with MongoMetaRecord[RefFieldTestRecord] {
416+
override def formats = allFormats
417+
}

persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala

+26
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,32 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M
511511
}
512512
}
513513

514+
"retrieve MongoRef objects properly" in {
515+
checkMongoIsRunning
516+
517+
val ntr = NullTestRecord.createRecord
518+
val btr = BoxTestRecord.createRecord
519+
520+
fttr.save
521+
ltr.save
522+
mtr.save
523+
ntr.save
524+
btr.save
525+
526+
val rftr = RefFieldTestRecord.createRecord
527+
.mandatoryObjectIdRefField(fttr.id.is)
528+
.mandatoryUUIDRefField(ltr.id.is)
529+
.mandatoryStringRefField(mtr.id.is)
530+
.mandatoryIntRefField(ntr.id.is)
531+
.mandatoryLongRefField(btr.id.is)
532+
533+
rftr.mandatoryObjectIdRefField.obj mustEqual Full(fttr)
534+
rftr.mandatoryUUIDRefField.obj mustEqual Full(ltr)
535+
rftr.mandatoryStringRefField.obj mustEqual Full(mtr)
536+
rftr.mandatoryIntRefField.obj mustEqual Full(ntr)
537+
rftr.mandatoryLongRefField.obj mustEqual Full(btr)
538+
}
539+
514540
}
515541
}
516542

0 commit comments

Comments
 (0)