Skip to content
Browse files

Issues 1117,1148,1149 - Add update method in MongoRecord, Add java.ut…

…il.logging.config system property to default build options, MongoRecord should reset dirty flags on save
  • Loading branch information...
1 parent 42ec31b commit 74ec930e714896766addffebc7dcdeda629c28a5 @eltimn eltimn committed
View
2 liftsh
@@ -9,7 +9,7 @@ fi
INTERNAL_OPTS="-Dfile.encoding=UTF-8 -Xss8M -Xmx1G -noverify -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:MaxPermSize=512M"
# Default options, if nothing is specified
-DEFAULT_OPTS="-Dsbt.intransitive=true"
+DEFAULT_OPTS="-Dsbt.intransitive=true -Djava.util.logging.config.file=logging.properties"
cd `dirname $0`
View
16 logging.properties
@@ -0,0 +1,16 @@
+# Specify the handlers to create in the root logger
+# (all loggers are children of the root logger)
+# The following creates two handlers
+handlers = java.util.logging.ConsoleHandler
+
+# Set the default logging level for the root logger
+.level = ALL
+
+# Set the default logging level for new ConsoleHandler instances
+java.util.logging.ConsoleHandler.level = INFO
+
+# Set the default formatter for new ConsoleHandler instances
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+
+# Set the default logging level for the named logger
+com.mongodb.level = SEVERE
View
60 persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/BsonRecord.scala
@@ -74,37 +74,45 @@ trait BsonMetaRecord[BaseRecord <: BsonRecord[BaseRecord]] extends MetaRecord[Ba
* using asDBObject
*/
def asDBObject(inst: BaseRecord): DBObject = {
+ val dbo = BasicDBObjectBuilder.start // use this so regex patterns can be stored.
+
+ for {
+ field <- fields(inst)
+ dbValue <- fieldDbValue(field)
+ } { dbo.add(field.name, dbValue) }
+
+ dbo.get
+ }
+ /**
+ * Return the value of a field suitable to be put in a DBObject
+ */
+ def fieldDbValue(f: Field[_, BaseRecord]): Box[Any] = {
import Meta.Reflection._
import field.MongoFieldFlavor
- val dbo = BasicDBObjectBuilder.start // use this so regex patterns can be stored.
-
- for (f <- fields(inst)) {
- f match {
- case field if (field.optional_? && field.valueBox.isEmpty) => // don't add to DBObject
- case field: EnumTypedField[Enumeration] =>
- field.asInstanceOf[EnumTypedField[Enumeration]].valueBox foreach {
- v => dbo.add(f.name, v.id)
- }
- case field: EnumNameTypedField[Enumeration] =>
- field.asInstanceOf[EnumNameTypedField[Enumeration]].valueBox foreach {
- v => dbo.add(f.name, v.toString)
- }
- case field: MongoFieldFlavor[Any] =>
- dbo.add(f.name, field.asInstanceOf[MongoFieldFlavor[Any]].asDBObject)
- case field => field.valueBox foreach (_.asInstanceOf[AnyRef] match {
- case null => dbo.add(f.name, null)
- case x if primitive_?(x.getClass) => dbo.add(f.name, x)
- case x if mongotype_?(x.getClass) => dbo.add(f.name, x)
- case x if datetype_?(x.getClass) => dbo.add(f.name, datetype2dbovalue(x))
- case x: BsonRecord[_] => dbo.add(f.name, x.asDBObject)
- case x: Array[Byte] => dbo.add(f.name, x)
- case o => dbo.add(f.name, o.toString)
- })
- }
+ f match {
+ case field if (field.optional_? && field.valueBox.isEmpty) => Empty // don't add to DBObject
+ case field: EnumTypedField[Enumeration] =>
+ field.asInstanceOf[EnumTypedField[Enumeration]].valueBox map {
+ v => v.id
+ }
+ case field: EnumNameTypedField[Enumeration] =>
+ field.asInstanceOf[EnumNameTypedField[Enumeration]].valueBox map {
+ v => v.toString
+ }
+ case field: MongoFieldFlavor[Any] =>
+ Full(field.asInstanceOf[MongoFieldFlavor[Any]].asDBObject)
+ case field => field.valueBox map (_.asInstanceOf[AnyRef] match {
+ case null => null
+ case x if primitive_?(x.getClass) => x
+ case x if mongotype_?(x.getClass) => x
+ case x if datetype_?(x.getClass) => datetype2dbovalue(x)
+ case x: BsonRecord[_] => x.asDBObject
+ case x: Array[Byte] => x
+ case o => o.toString
+ })
}
- dbo.get
}
/**
View
51 persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoMetaRecord.scala
@@ -23,7 +23,7 @@ import java.util.regex.Pattern
import scala.collection.JavaConversions._
-import net.liftweb.common.{Box, Empty, Full}
+import net.liftweb.common._
import net.liftweb.json.{Formats, JsonParser}
import net.liftweb.json.JsonAST._
import net.liftweb.mongodb._
@@ -232,6 +232,7 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]]
foreachCallback(inst, _.beforeSave)
f
foreachCallback(inst, _.afterSave)
+ inst.allFields.foreach { _.resetDirty }
true
}
@@ -239,7 +240,7 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]]
* Save the instance in the appropriate backing store
*/
def save(inst: BaseRecord, concern: WriteConcern): Boolean = saveOp(inst) {
- useColl { coll =>
+ useColl { coll =>
coll.save(inst.asDBObject, concern)
}
}
@@ -314,4 +315,50 @@ trait MongoMetaRecord[BaseRecord <: MongoRecord[BaseRecord]]
.get)
this.update(query, update)
}
+
+ /**
+ * Update only the dirty fields
+ */
+ def update(inst: BaseRecord): Unit = {
+ val dirtyFields = fields(inst).filter(_.dirty_?)
+ if (dirtyFields.length > 0) {
+ val (fullFields, otherFields) = dirtyFields
+ .map(field => (field.name, fieldDbValue(field)))
+ .partition(pair => pair._2.isDefined)
+
+ val fieldsToSet = fullFields.map(pair => (pair._1, pair._2.open_!)) // these are all Full
+
+ val fieldsToUnset: List[String] = otherFields.filter(
+ pair => pair._2 match {
+ case Empty => true
+ case _ => false
+ }
+ ).map(_._1)
+
+ if (fieldsToSet.length > 0 || fieldsToUnset.length > 0) {
+ val dbo = BasicDBObjectBuilder.start
+
+ if (fieldsToSet.length > 0) {
+ dbo.add(
+ "$set",
+ fieldsToSet.foldLeft(BasicDBObjectBuilder.start) {
+ (builder, pair) => builder.add(pair._1, pair._2)
+ }.get
+ )
+ }
+
+ if (fieldsToUnset.length > 0) {
+ dbo.add(
+ "$unset",
+ fieldsToUnset.foldLeft(BasicDBObjectBuilder.start) {
+ (builder, fieldName) => builder.add(fieldName, 1)
+ }.get
+ )
+ }
+
+ update(inst, dbo.get)
+ dirtyFields.foreach { _.resetDirty }
+ }
+ }
+ }
}
View
10 persistence/mongodb-record/src/main/scala/net/liftweb/mongodb/record/MongoRecord.scala
@@ -74,6 +74,16 @@ trait MongoRecord[MyType <: MongoRecord[MyType]] extends BsonRecord[MyType] {
def save: MyType = save(false)
/**
+ * Update only the dirty fields
+ */
+ def update: MyType = {
+ runSafe {
+ meta.update(this)
+ }
+ this
+ }
+
+ /**
* Delete the instance from backing store
*/
def delete_! : Boolean = {
View
9 persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/Fixtures.scala
@@ -149,6 +149,8 @@ class FieldTypeTestRecord private () extends MongoRecord[FieldTypeTestRecord] wi
this.mandatoryTimeZoneField.value == that.mandatoryTimeZoneField.value
case _ => false
}
+
+ def dirtyFields = this.allFields.filter(_.dirty_?)
}
object FieldTypeTestRecord extends FieldTypeTestRecord with MongoMetaRecord[FieldTypeTestRecord]
@@ -252,6 +254,8 @@ class MongoFieldTypeTestRecord private () extends MongoRecord[MongoFieldTypeTest
this.mandatoryUUIDField.value == that.mandatoryUUIDField.value
case _ => false
}
+
+ def dirtyFields = this.allFields.filter(_.dirty_?)
}
object MongoFieldTypeTestRecord extends MongoFieldTypeTestRecord with MongoMetaRecord[MongoFieldTypeTestRecord] {
@@ -291,6 +295,8 @@ class ListTestRecord private () extends MongoRecord[ListTestRecord] with UUIDPk[
this.mandatoryMongoJsonObjectListField.value == that.mandatoryMongoJsonObjectListField.value
case _ => false
}
+
+ def dirtyFields = this.allFields.filter(_.dirty_?)
}
object ListTestRecord extends ListTestRecord with MongoMetaRecord[ListTestRecord] {
override def formats = allFormats
@@ -314,6 +320,8 @@ class MapTestRecord private () extends MongoRecord[MapTestRecord] with StringPk[
this.mandatoryIntMapField.value == that.mandatoryIntMapField.value
case _ => false
}
+
+ def dirtyFields = this.allFields.filter(_.dirty_?)
}
object MapTestRecord extends MapTestRecord with MongoMetaRecord[MapTestRecord] {
override def formats = allFormats
@@ -391,6 +399,7 @@ class SubRecordTestRecord private () extends MongoRecord[SubRecordTestRecord] wi
case _ => false
}
+ def dirtyFields = this.allFields.filter(_.dirty_?)
}
object SubRecordTestRecord extends SubRecordTestRecord with MongoMetaRecord[SubRecordTestRecord] {
override def formats = allFormats
View
163 persistence/mongodb-record/src/test/scala/net/liftweb/mongodb/record/MongoRecordSpec.scala
@@ -618,6 +618,169 @@ object MongoRecordSpec extends Specification("MongoRecord Specification") with M
r.optionalTimeZoneField.is must beEmpty
}
}
+
+ "reset dirty flags on save" in {
+ val fttr = FieldTypeTestRecord.createRecord.save
+ fttr.mandatoryDecimalField(BigDecimal("3.14"))
+ fttr.dirtyFields.length must_== 1
+ fttr.save
+ fttr.dirtyFields.length must_== 0
+ }
+
+ "update dirty fields for a FieldTypeTestRecord" in {
+ val fttr = FieldTypeTestRecord.createRecord
+ .legacyOptionalStringField("legacy optional string")
+ .optionalStringField("optional string")
+ .save
+
+ fttr.mandatoryBooleanField(false)
+ fttr.mandatoryDecimalField(BigDecimal("3.14"))
+ fttr.mandatoryDoubleField(1999)
+ fttr.mandatoryEnumField(MyTestEnum.ONE)
+ fttr.mandatoryIntField(99)
+ fttr.mandatoryLongField(100L)
+ fttr.mandatoryStringField("string")
+ fttr.optionalStringField(Empty)
+ fttr.legacyOptionalStringField(Empty)
+
+ fttr.dirtyFields.length must_== 9
+ fttr.update
+ fttr.dirtyFields.length must_== 0
+
+ val fromDb = FieldTypeTestRecord.find(fttr.id.is)
+ fromDb must notBeEmpty
+ fromDb foreach { rec =>
+ rec must_== fttr
+ rec.dirtyFields.length must_== 0
+ }
+
+ val fttr2 = FieldTypeTestRecord.createRecord.save
+
+ fttr2.legacyOptionalStringField("legacy optional string")
+ fttr2.optionalStringField("optional string")
+
+ fttr2.dirtyFields.length must_== 2
+ fttr2.update
+ fttr2.dirtyFields.length must_== 0
+
+ val fromDb2 = FieldTypeTestRecord.find(fttr2.id.is)
+ fromDb2 must notBeEmpty
+ fromDb2 foreach { rec =>
+ rec must_== fttr2
+ rec.dirtyFields.length must_== 0
+ }
+ }
+
+ "update dirty fields for a MongoFieldTypeTestRecord" in {
+ val mfttr = MongoFieldTypeTestRecord.createRecord
+ .legacyOptionalDateField(new Date)
+ .legacyOptionalObjectIdField(ObjectId.get)
+ .save
+
+ mfttr.mandatoryDateField(new Date)
+ mfttr.mandatoryJsonObjectField(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")))
+ mfttr.mandatoryObjectIdField(ObjectId.get)
+ mfttr.mandatoryPatternField(Pattern.compile("^Mon", Pattern.CASE_INSENSITIVE))
+ mfttr.mandatoryUUIDField(UUID.randomUUID)
+ mfttr.legacyOptionalDateField(Empty)
+ mfttr.legacyOptionalObjectIdField(Empty)
+
+ mfttr.dirtyFields.length must_== 7
+ mfttr.update
+ mfttr.dirtyFields.length must_== 0
+
+ val fromDb = MongoFieldTypeTestRecord.find(mfttr.id.is)
+ fromDb must notBeEmpty
+ fromDb foreach { rec =>
+ rec must_== mfttr
+ rec.dirtyFields.length must_== 0
+ }
+
+ val mfttr2 = MongoFieldTypeTestRecord.createRecord.save
+
+ mfttr2.legacyOptionalDateField(new Date)
+ mfttr2.legacyOptionalObjectIdField(ObjectId.get)
+
+ mfttr2.dirtyFields.length must_== 2
+ mfttr2.update
+ mfttr2.dirtyFields.length must_== 0
+
+ val fromDb2 = MongoFieldTypeTestRecord.find(mfttr2.id.is)
+ fromDb2 must notBeEmpty
+ fromDb2 foreach { rec =>
+ rec must_== mfttr2
+ rec.dirtyFields.length must_== 0
+ }
+ }
+
+ "update dirty fields for a ListTestRecord" in {
+ val ltr = ListTestRecord.createRecord.save
+
+ ltr.mandatoryStringListField(List("abc", "def", "ghi"))
+ ltr.mandatoryIntListField(List(4, 5, 6))
+ ltr.mandatoryMongoJsonObjectListField(List(TypeTestJsonObject(1, "jsonobj1", Map("x" -> "1")), TypeTestJsonObject(2, "jsonobj2", Map("x" -> "2"))))
+ ltr.mongoCaseClassListField(List(MongoCaseClassTestObject(1,"str")))
+
+ ltr.dirtyFields.length must_== 4
+ ltr.update
+ ltr.dirtyFields.length must_== 0
+
+ val fromDb = ListTestRecord.find(ltr.id.is)
+ fromDb must notBeEmpty
+ fromDb foreach { rec =>
+ rec must_== ltr
+ rec.dirtyFields.length must_== 0
+ }
+ }
+
+ "update dirty fields for a MapTestRecord" in {
+ val mtr = MapTestRecord.save
+
+ mtr.mandatoryStringMapField(Map("a" -> "abc", "b" -> "def", "c" -> "ghi"))
+ mtr.mandatoryIntMapField(Map("a" -> 4, "b" -> 5, "c" -> 6))
+
+ mtr.dirtyFields.length must_== 2
+ mtr.update
+ mtr.dirtyFields.length must_== 0
+
+ val fromDb = MapTestRecord.find(mtr.id.is)
+ fromDb must notBeEmpty
+ fromDb foreach { rec =>
+ rec must_== mtr
+ rec.dirtyFields.length must_== 0
+ }
+ }
+
+ "update dirty fields for a SubRecordTestRecord" in {
+ val srtr = SubRecordTestRecord.createRecord.save
+
+ val ssr1 = SubSubRecord.createRecord.name("SubSubRecord1")
+ val ssr2 = SubSubRecord.createRecord.name("SubSubRecord2")
+
+ val sr1 = SubRecord.createRecord
+ .name("SubRecord1")
+ .subsub(ssr1)
+ .subsublist(ssr1 :: ssr2 :: Nil)
+ .slist("s1" :: "s2" :: Nil)
+ .smap(Map("a" -> "s1", "b" -> "s2"))
+ .pattern(Pattern.compile("^Mon", Pattern.CASE_INSENSITIVE))
+
+ val sr2 = SubRecord.createRecord.name("SubRecord2")
+
+ srtr.mandatoryBsonRecordField(sr1)
+ srtr.mandatoryBsonRecordListField(List(sr1,sr2))
+
+ srtr.dirtyFields.length must_== 2
+ srtr.update
+ srtr.dirtyFields.length must_== 0
+
+ val fromDb = SubRecordTestRecord.find(srtr.id.is)
+ fromDb must notBeEmpty
+ fromDb foreach { rec =>
+ rec must_== srtr
+ rec.dirtyFields.length must_== 0
+ }
+ }
}
}

0 comments on commit 74ec930

Please sign in to comment.
Something went wrong with that request. Please try again.