Zen and the art of KVC

Key Value Coding – it’s one of the reasons WebObjects is so much fun.

The Basics

For those uninitiated, KVC allows your code to access method, attributes, or variables in your classes using string keys. When you are binding to a WOElement like WOString in WebObjects Builder you are using KVC (WOString, in-case you didn’t know, is a "String" with "WO"… in-joke, sorry). But KVC can help you a lot in your own code.

For instance, this:

public String myFirstName() {
	return person.firstName();
}

can be re-written as this:

Person person; // assume exists and implements NSKeyValueCoding
  
public String myFirstName() {
	return (String)person.valueForKey("firstName");
}

You can also set the value on an object using:

public String setMyFirstName(String newName) {
	person.takeValueForKey(newName, "firstName");
}

Why bother? Well for one thing, when using KVC you don’t need to know the type of the object. So this code would work just as well:

EOEnterpriseObject eo; // assume exists
  
public String myFirstName() {
	return (String)eo.valueForKey("firstName");
}

Meaning the eo could be a Person, or an Employee, or a Customer, or any other object that returns a value for firstName. This becomes very useful when you are writing reusable objects, handling conditional navigation in your components (ie: when the destination component will not be determined until run-time), or creating bindings for reusable components.

How does WebObjects find a match for your key? The KVC implementation in WebObjects will match using these rules:

  • When "getting" values:
    1. a method named firstName or getFirstName
    2. a variable named firstName or _firstName
  • When "setting" values:
    1. a method named setFirstName
    2. a variable named firstName or _firstName

Based on these rules, I like to name my accessors: varName() and their variables: _varName so there is no confusion about which will be hit by KVC.

Following the Path

In addition to valueForKey and takeValueForKey, you can access values through their paths using valueForKeyPath and takeValueForKeyPath. Lets say that you have an order that has an approver who has an address. You could access the address like this:

Order order; // assume exists
  
public String approverAddress() {
	return (Address)order.valueForKeyPath("approver.address");
}

If you were in a WOComponent or some other object that implemented the NSKeyValueCoding interface, you could use code like this:

public String approverAddress() {
	return (Address)this.valueForKeyPath(
			"order.approver.address");
}

This example demonstrates why KVC takes me to my happy place: If any of the objects in the path are optional, KVC will gracefully handle the nulls. So the code above could potentially replace:

public String approverAddress() {
	if (order != null) {
		Person approver = order.approver();
		if (approver != null) {
			return approver.address();
		}
	}
	return null;
}

A Strong Foundation

Key Value Coding is implemented in many of the Foundation classes like NSArray, NSDictionary, etc. As an example, if you have an NSArray called orders that contains order objects that all need to be canceled by setting their cancelFlag to true. You could use:

NSArray orders; // assume exists
  
public String cancelOrders() {
	orders.takeValueForKey(Boolean.TRUE, "cancelFlag");
}

Couple this with valueForKeyPath and takeValueForKeyPath you have a very powerful set of additional tools at your disposal.

Putting it all together

Here is a final example using KVC and the NSArray operators from ProjectWONDER that I mentioned previously here.

Perhaps we have this:

order <-->> product <-->> sku

We want to get all of the skus. Each order will have several products but not all products will have skus. Using traditional java we’d end up having to do something like this:

public NSArray allSkus() {
	NSArray products = order.products();
	int productCount = products.count();
	NSMutableArray allSkus = new NSMutableArray();
	for (int i = 0; i < productCount; i ++) {
		Product product = products.objectAtIndex(i);
		NSArray theseSkus = product.skus();
		if (theseSkus != null && theseSkus.count() > 0) {
			allSkus.addObjectsFromArray(theseSkus);
		}
	}
	return allSKus;
}

Using KVC and the @flatten opperator from ProjectWONDER we can do something like this:

public NSArray allSkus() {
	return (NSArray)order.valueForKeyPath(
				"products.skus.@flatten");
}

First we ask the order for its products, then we ask the products for their skus. KVC will discard any null results, and we end up with an NSArray of NSArrays of skus. The final @flatten operator will flatten this nested array into a single NSArray of skus.

Want the result sorted by skuNumber? No problem:

public NSArray allSkus() {
	NSArray allSkus = (NSArray)order.valueForKeyPath(
				"products.skus.@flatten");
	return (NSArray) allSkus.valueForKeyPath(
				"@sortAsc.skuNumber");
}

Embracing Key Value Coding can reduce the code you write and make your code more robust. It can also help you work the "WebObjects Way".

I highly recommend it.

Note: If you feel uncomfortable implementing all of ProjectWONDER just to get the NSArray operators, don’t. Just add the ERXArrayUtilties.java file to your project and call ERXArrayUtilties.initialize(); in your Application’s constructor.

G5 – –

New PowerBooks… They max out at 1.67GHz G4.

Decent speed bump, and some nifty new technologies – scrolling trackpad for instance. Hopefully this will quell the G5 PB rumours for a week or two… Oh, I forgot about the conspiracy theorists.

Programatically creating a WODisplayGroup (take 2)

I wrote about programatically creating WODisplayGroups here. In that example I use a find() method to explicitly do a fetch, and those objects were assigned to the the WODisplayGroup.objectArray().

That is just my preferred way of handling the object fetch, but of course it is not the only way. If you wanted to emulate what happens if you create a WODisplayGroup by dragging an Entity from EOModeler into WebObjects Builder, you might do something like this:

protected WODisplayGroup _displayGroup;
protected EOEditingContext ec;   //Assume exists
protected String dateSourceName; //Name of the Entity
 
public WODisplayGroup displayGroup()
{
    if (_displayGroup == null) {
        _displayGroup = new WODisplayGroup();
        _displayGroup.setNumberOfObjectsPerBatch(batchSize);
        EODatabaseDataSource ds = new EODatabaseDataSource(
					ec, 
					dataSourceName);
        _displayGroup.setDataSource(ds);
        _displayGroup.fetch();
    }
    return _displayGroup;
}

You could also create a WODisplayGroup based on a master detail relationship like this:

protected WODisplayGroup _displayGroup;
protected EOEnterpriseObject masterObject; //The Master object
protected String detailKey; //The name of the relationship 
                            //in the masterObject that will
                            //provide the _displayGroup's objects
 
public WODisplayGroup displayGroup()
{
    if (_displayGroup == null) {
        _displayGroup = new WODisplayGroup();
        _displayGroup.setNumberOfObjectsPerBatch(batchSize);
        EODetailDataSource dds = new EODetailDataSource(
			masterObject.classDescription(),
			detailKey);
        _displayGroup.setDataSource(dds);
        _displayGroup.setMasterObject(masterObject);
        _displayGroup.setDetailKey(detailKey);
        _displayGroup.fetch();
    }
    return _displayGroup;
}

As before the WODisplayGroup API is here

RSS Full Posts?

Tom Bridge asks:

“Any chance I could get you to run full feeds of your posts in your RSS and not just titles?”

Some of my posts can get rather long so currently the RSS feed is set to summarize which either truncates the post or (if I provide one) shows the excerpt.

I don’t really care one way or another but I’m willing to put this to a vote. Do you want me to continue summarizing the RSS feeds, or do you want the full posts? I’ll give this post a week and then make my decision. The majority vote in the comments will win (only one vote per person please).

Alternately, if anyone knows of a WordPress template that will provide a separate full post RSS feed along with the default one let me know and I’ll look at implementing it.

iTunes Music Store Canada

We’re coming up on the second month anniversary of the Canadian iTMS opening and I am still rather disappointed with the selection. There is a ton of music missing that exists on the US or UK stores.

In just a random search: No New Pornographers, the Bleed American album by Jimmy Eat World is missing (although thier others are there), no Gary Numan, and no JoyDrop. I was hoping that iTMS would enable the long tail. Dissapointing.

I’m assuming that there are a bunch of labels that haven’t cleared their music for iTMSCA yet. I was hoping things would get better in January. Sigh, I guess patience is not my strong suite…