ERXWORepetition + checkHashCodes

35 views
Skip to first unread message

Mike Schrag

unread,
Apr 11, 2007, 6:55:39 PM4/11/07
to wot...@googlegroups.com
When you use WORepetition w/ Wonder, you're actually using the patched in ERXWORepetition.  One of the cool features that ERXWORepetition has is "checkHashCodes" mode.

In WO, repetition items are referenced by index normally.  That is to say, when you look at the element ID of something in a repetition, they are like 1.5.0, 1.5.1, 1.5.2, 1.5.3, incrementing for each entry in the repetition.  The problem with reference-by-index is that if your underlying data changes between requests, your indexes no longer match.

Take, for example, a WORepetition of Person EO's.  Say there are 10 people in the list.  You request the page and see the list of 10.  While you're waiting, someone deletes Person #5 behind the scenes.  You now decide YOU want to delete Person #5.  However, when you click on the delete action, that actually ends up now corresponding to Person #6 (because the index said 5, but the 5 entry is gone, and now Person 6 is bumped down into that slot).  Uh oh. You just deleted the wrong person.  There are lots of ways to work around this problem, but they generally require some form of caching the array in the component (WODisplayGroup, holding onto the original NSArray, etc), which presents freshness issues.  It's fairly common that people just bind to "company.people" as the list, which can very possibly change underneath you.  Granted this tends not to be a common problem because people don't often sit on pages with collaborative data for extended periods of time (without refreshing), but it can be a catastrophic problem if it does bites you.

Wonder provides some tricks to make this less of a problem in the form of checkHashCodes.  On a WORepetition (secretely ERXWORepetition), you can set checkHashCodes = true; and raiseOnUnmatchedObject=true:

    <wo:WORepetition list = "$company.people" item = "$repetitionPerson" checkHashCodes = "$true" raiseOnUnmatchedObject = "$true">
      ...
    </wo:WORepetition>

Now instead of generating index-based element ids, it will generate identityHashCode-based element ids (by default).  Note that the default behavior is not a fort-knox prevention technique, because technically you can have hashcode collisions, but this is banking on it being very unlikely that you will end up with two objects in the same list that happen to have the same identity hash code.  If you want to "lock down" your matches, you can use the "uniqueKey" binding (this is new, you might need to update to get it).  uniqueKey lets you bind a string keypath relative to your items that will return a unique integer identifier for you object.  If you have an array of EOs, using ERXGenericRecord, and your EOs have integer keys, you can specify uniqueKey = "rawPrimaryKey", as an example.  This will now match the selection against primaryKey and will always unique properly.  While this "reveals" primary key to your end users, the set of matches is restricted to those that are in the list, so no additional capabilities are directly exposed.

raiseOnUnmatchedObject = true says that an exception should be thrown when no matching object is found (rather than just ignoring the request).

If you want this behavior everywhere, you can set the Properties:

    er.extensions.ERXWORepetition.checkHashCodes=true
    er.extensions.ERXWORepetition.raiseOnUnmatchedObject=true

One thing to be careful of is if you have a list that regenerates every pass through your RR loop.  For instance, if you have a list where the items are dynamically generated, then you can run into a situation where no matches are ever found because their hashcodes keep changing.  In these cases (which for me seems to be rare) you can either cache your list items so they don't regenerate, or you can explicitly set the checkHashCodes to false for that particular repetition (assuming you enabled this feature globally).  Alternatively, you can use the uniqueKey binding to bind to a specific unchanging keypath on your dynamic objects.  Note that EO's within the same EC do not have this problem -- it's more likely that if you have a repetition where the items are collections (arrays or dictionaries) that you are creating on-the-fly.

This feature defaults off in Wonder because of the above catch -- it is possible that globally enabling it will break certain repetitions.  However, it's very much worth trying it out in your app, because the possible terrible calamity that can result by not using this often outweigh the testing to find the few odd cases that explode with it.

ms
Reply all
Reply to author
Forward
0 new messages