squery-record & dirty

90 views
Skip to first unread message

Marcin Mielżyński

unread,
Apr 14, 2012, 3:50:33 PM4/14/12
to lif...@googlegroups.com
Hi, I noticed that RecordMetaDataFactory uses setFromAny to populate the
fields which in turn flags "dirty_?" as true and makes it pretty useless.
Do you have an idea how to work it around ?

Thanks!

David Whittaker

unread,
Apr 14, 2012, 4:27:41 PM4/14/12
to lif...@googlegroups.com
I'm afraid that whoever originally put the Squeryl Record module together doesn't seem to have been aware of the dirty_? flag.  This came up once before, but you are the first person who it seems to be causing a problem for.  Can you tell me what you are using the dirty_? flag for?  Maybe I can come up with a work around for you.

-Dave

2012/4/14 Marcin Mielżyński <lo...@gazeta.pl>
Hi, I noticed that RecordMetaDataFactory uses setFromAny to populate the fields which in turn flags "dirty_?" as true and makes it pretty useless.
Do you have an idea how to work it around ?

Thanks!


--
Lift, the simply functional web framework: http://liftweb.net
Code: http://github.com/lift
Discussion: http://groups.google.com/group/liftweb
Stuck? Help us help you: https://www.assembla.com/wiki/show/liftweb/Posting_example_code

Marcin Mielżyński

unread,
Apr 14, 2012, 5:47:13 PM4/14/12
to lif...@googlegroups.com
I'm working on a more flexible crud for record-squeryl where one can specify fields for list/create/view etc. The problem is with partial update were I want to filter fields based on "dirty_?" flag out.
The use case is of course when user changes only one field in the form (the rest gets posted too but the field can check that new value for equality with an old one and leave the "dirty_?" flag intact).
Also, the problem becomes more apparent when ajaxified fields are used on list views, one would like to update only one field in the db when doing 'record.save'.

I did similar thing few years ago using Mapper/Crudify and all that logic was in Mapper fields already, worked like a charm.

I'm aware that squeryl can't be made "dirty" aware since it operates on primitive fields, but Record Fields can have more context, you could do then:

class LongField extends .... {
   def assignment: org.squeryl.dsl.ast.UpdateAssignment = this := get // typsafe assignment for fields
}

def assign(rec: Record): Seq[org.squeryl.dsl.ast.UpdateAssignment] = rec.fields.filter(_.dirty_?).map(_.assignment)

CRUDMetaRecord.table.update(r => where(r.id === 1) set(assign(r): _*))

W dniu 2012-04-14 22:27, David Whittaker pisze:

David Whittaker

unread,
Apr 16, 2012, 11:58:48 AM4/16/12
to lif...@googlegroups.com
Interesting.  So what you need is for Squeryl to reset the dirty state after retrieving the field values?  Sounds reasonable to me.  Please open an issue on github referencing this thread and post the issue number back here.

Thanks,
Dave

2012/4/14 Marcin Mielżyński <lo...@gazeta.pl>

Marcin Mielżyński

unread,
Apr 18, 2012, 5:42:35 PM4/18/12
to lif...@googlegroups.com
My use case (wrt https://github.com/lift/framework/issues/1259) requires two things:
1) having centralized equality check for new value (when the field is being set). This requires having setBox overridden like this:

trait FieldExtensions[T] extends TypedField[T] { ....
    def assignment: org.squeryl.dsl.ast.UpdateAssignment

    var dirty__? = false // second dirty value that I control, your fix to have the original one being left untouched while populated will make possible to get rid of this one.

    override def setBox(in: Box[T]): Box[T] = {
       val data = FieldExtensions.dataField.invoke(this).asInstanceOf[Box[T]]
        if (!data.isEmpty && data != in) dirty__? = true
        super.setBox(in)
    }
}

Without direct (or via getter) access I cannot reliably check for comparison since accessors like get/is return "value" method which in turn is "def value: MyType = valueBox openOr defaultValue".

2) being able to abstract over === for id/idField for MetaRecord so I can:

trait SquerylMetaRecord[K, BaseRecord <: Record[BaseRecord] with KeyedEntity[K]] extends MetaRecord[BaseRecord] {
    self: BaseRecord =>

    type KeyType = K

    def table: Table[BaseRecord]

    def collectionName = table.name // abstracted to my own MetaRecord so CRUD is not squeryl specific (can be reused for Mongo-Record, etc)

    ......

    def updatePartial(record: BaseRecord) {
        def assign(rec: BaseRecord): Seq[dsl.ast.UpdateAssignment] = rec.fields.collect{case f: FieldExtensions[_] if f.dirty__? => f}.map(_.assignment) // as "dirty" marker trait for now
        inTransaction{
            // here, although I have all the set assignments in place, I also need "find by id" (I have no idea what constraints should I put here on K to have reasonably typed def ===, I thought about unused now trait KeyField from record)
            table.update(r => where(r.id === record.id) set(assign(a): _*)

            // full update via table.update(o: T)(implicit ev: T <:< KeyedEntity[_]) makes no trouble here since it's delegated to squeryl which doesn't use sql-dsl for it
        }
    }

}


To show the whole picture, here is abstracted crudify that doesn't know about squeryl:

import net.liftweb._
import record.{Field}
import common.{Box, Empty, Full}
import scala.xml.NodeSeq

trait RecordCrudify[BaseRecord <: Record[BaseRecord]] extends BaseCrudify {
    self: MetaRecord[BaseRecord] =>

    type TheCrudType = BaseRecord
    type FieldPointerType = Field[_, TheCrudType]

    override def calcPrefix = collectionName :: Nil

    override def fieldsForDisplay: List[FieldPointerType] = metaFields.filter(_.shouldDisplay_?)

    override def computeFieldFromPointer(instance: TheCrudType, pointer: FieldPointerType): Box[FieldPointerType] = instance.fieldByName(pointer.name)

    override def create = createRecord

    // bridge replacements, the brigdes are not needed after all
    override def buildBridge(in: TheCrudType): CrudBridge = sys.error("buildBridge is turned off")
    override def buildFieldBridge(from: FieldPointerType): FieldPointerBridge = sys.error("buildFieldBridge is turned off")

    override def crudValidate(in: TheCrudType) = in.validate
    override def crudDisplayHtml(in: FieldPointerType): NodeSeq = in.displayHtml

    override def findForParam(in: String): Box[BaseRecord] = findByString(in)
    override def findForList(start: Long, count: Int) = findRange(start, count)

    // bridge replacements
    protected override def crudSave(in: BaseRecord) = save(in)
    protected override def crudDelete(in: BaseRecord) = delete(in)
    override def obscurePrimaryKey(in: BaseRecord): String = idToString(in)
}

a MetaRecord that doesn't know about crud:

import net.liftweb.record.{MetaRecord => OMetaRecord}
import net.liftweb.common.Box

trait MetaRecord[BaseRecord <: Record[BaseRecord]] extends OMetaRecord[BaseRecord] {
    self: BaseRecord =>

    type KeyType

    def collectionName: String

    def idFromString(in: String): KeyType // (implicit provider: IdProvider[KeyType]) = provider.idFromString(in)//
    def idToString(in: BaseRecord): String

    def findByString(in: String): Box[BaseRecord]
    def findRange(start: Long, count: Int): List[BaseRecord]

    def save(record: BaseRecord): Boolean
    def delete(record: BaseRecord): Boolean
}


and SquerylMetaRecord:

trait SquerylMetaRecord[K, BaseRecord <: Record[BaseRecord] with KeyedEntity[K]] extends MetaRecord[BaseRecord] {
    self: BaseRecord =>

    type KeyType = K

    def table: Table[BaseRecord]

    def collectionName = table.name

    def idToString(in: BaseRecord) = in.id.toString

    def findByString(in: String) = inTransaction {
        table.lookup(idFromString(in))
    }

    def findRange(start: Long, count: Int) = inTransaction {
        query(t => select(t)).page(start.toInt, count).toList
    }

    def save(record: BaseRecord) = {
        if (record.isPersisted) {
            inTransaction{table.update(record)} // I don't want to perform full update here
        } else {
            inTransaction{table.insert(record)}
        }
        true
    }

    def delete(record: BaseRecord) = table.delete(record.id)

    def query[R](f: BaseRecord => dsl.QueryYield[R]): Query[R] = from(table)(f)
}

Thanks,


W dniu 2012-04-16 17:58, David Whittaker pisze:

David Whittaker

unread,
Apr 19, 2012, 9:52:56 AM4/19/12
to lif...@googlegroups.com
Marcin,

I had seen your code before, but rather than digging through it and trying to figure out what you are trying to accomplish, I was hoping for a short description, i.e. "A user posts a form where only some values have changed.  Record only marks as dirty the fields that are different.  The update call only sends dirty fields to the DB.".  Does that about sum it up?

2012/4/18 Marcin Mielżyński <lo...@gazeta.pl>

Marcin Mielżyński

unread,
Apr 19, 2012, 5:05:23 PM4/19/12
to lif...@googlegroups.com
Essentially yes (or even being able to choose between full and partial updates), the posted code was to show why I care.

Thanks.

W dniu 2012-04-19 15:52, David Whittaker pisze:

David Whittaker

unread,
Apr 24, 2012, 10:41:08 AM4/24/12
to lif...@googlegroups.com
Hi Marcin,

Ok... I've got a clearer picture now.  I think.  I'll make the updates to Squeryl-Record soon so that the fields don't begin in the dirty state. Implementing a check regarding whether a value has really changed when a Record Field is updated is a bit more involved though.  I'd like to think that Record.set_! could be modified so that it performs an == chacke, rather than setting dirty true whenever setBox is called.  I'm hesitant to make that change though since I don't know what affect it might have on other Record modules like Mongo.  I'm going to think about this one and get back to you.

2012/4/19 Marcin Mielżyński <lo...@gazeta.pl>

Marcin Mielżyński

unread,
May 8, 2012, 1:40:56 PM5/8/12
to lif...@googlegroups.com
Thank you! That's really appreciated.

W dniu 2012-04-24 16:41, David Whittaker pisze:

David Whittaker

unread,
May 8, 2012, 1:48:03 PM5/8/12
to lif...@googlegroups.com
No problem.  We discussed it in another thread and I'm planning to add the == check in setBox as well: https://github.com/lift/framework/issues/1265.
Reply all
Reply to author
Forward
0 new messages