WebObjects – Flawed beginner info

An EO out of an EC is like a fish out of water

The books WebObjects beginners usually read when they are starting out are the Apple Web Applications tutorial and Joshua Marker’s WebObjects 5 for Mac OS X: Visual QuickPro Guide.

Both are very good introductions. They cover a great deal of territory (and there is a great deal of territory to cover when you’re beginning).

However, they both share a fatal flaw. Both endorse the practice of inserting EnterpriseObjects (EOs) into their EOEditingContexts (ECs) late.

By late I mean: NOT RIGHT AWAY!

The EOF Commandments are very clear on this. Many – myself included – have the nasty experiences to back this up. It is never a good idea to defer adding a newly created EO into an EC. In fact, Chuck Hill is quoted as saying: “I’ll opine that an EO not in an EC is in fact not an EO just a plain old Object.”

That’s a lot to give up. No change notification, no EOGobalID, no undo, no guarantee that awakeFromInsertion is not going to hose any changes you’ve made…

OK, to be fair, the examples in these documents work. It is possible to set some values in an EO prior to inserting it into an EC and have it work.

Maybe.

Some of the time.

But don’t count on it.

And that’s the problem, there are no rules to dictate when it is safe and when it’s not. So, if you are looking for best practices: Just don’t do it – ever.

Learn the correct way first.

I find the Apple Web Applications tutorial is particularly bad in this instance. Not only do they advise inserting the EO into it’s EC late, but they develop a UI around the practice in such a way that if you try to reuse your learning in your own projects (by creating a similar UI) you will be trapped.

Take a look at the UI their Authors tutorial uses:

web_app.gif

Now, I’m not going to spend a lot of time on how confusing this is. I’ve got to assume this is meant just as a learning example, but jeez-louise they could have done it right without any more effort. Honestly, can you tell what the Update, Add, and Save buttons are suppose to do? I can’t.

Anyway, lets look at some of the code underneath this thing:

public Main(WOContext context) { 
    super(context); 
    // Build the fetch specification. 
    fetchSpec = new EOFetchSpecification("Author", null, null); 
    // Get the editing context. 
    editingContext = session().defaultEditingContext(); 
    // Fetch authors. 
    authorList = new NSMutableArray(
        editingContext.objectsWithFetchSpecification(fetchSpec)); 
    // Create an Author object (where form data is stored). 
    author = new Author(); 
}

OK, so this is the component constructor. It fetches the array of authors from using a fetchSpec – I can handle that. Then it creates an empty Author without inserting it into an EC. Bad, bad, bad.

public WOComponent addAuthor() { 
    // Add the author only when it isn’t in the list. 
    if (!authorList.containsObject(author)) { 
        // Add the author to the list. 
        authorList.addObject(author); 
        // Insert author into editing context. 
        editingContext.insertObject(author); 
        // Create a new author. 
        author = new Author(); 
    } 
    return null; 
}

Oh, look, there it is again. Let me check… yup still bad.

public WOComponent updateAuthor() { 
    // Create an Author object. 
    author = new Author(); 
    return null; 
}

And yet again. Umm… nope, still bad.

I can kinda see why they’re doing this. The author needed an object to bind to the form that wouldn’t get saved to the DB when the user clicked save unless explicitly added to the list first. Instead of dealing with the complexity of multiple ECs or using NSMutableDictionaries as proxy objects he did this. And yes, it works in this instance, but that doesn’t stop it from being a bad practice and furthermore it teaches inexperienced developers a bad lesson.

But really, I think the root of the problem is with the design of the UI. If we separate the list and edit functions into two user modes (Yes I know, very Web 1.0, but this is a beginners tutorial!) the need that led to this problem goes away.

Here let me show you.

First some mockups of the pages. AuthorList first:

authors_list.gif

Then AuthorEdit:

author_details.gif

Now the relevent code for the AuthorList (I’m assuming that the list is populated and the WORepetition has all it needs):

public WOComponent addAuthor() { 
    // Create an Author object (properly!). 
    Author author = (Author)EOUtilities.createAndInsertInstance(
        ec(), "Author"); 
    // Where are we going? (Planet 10!)
    AuthorEdit nextPage = (AuthorEdit)pageWithName("AuthorEdit");
    // Pass in the new author
    nextPage.setAuthor(author);
    // And go there
    return nextPage; 
}
 
public WOComponent editAuthor() { 
    // Where are we going?
    AuthorEdit nextPage = (AuthorEdit)pageWithName("AuthorEdit");
    // Pass in the selected author
    nextPage.setAuthor(authorItem);
    return nextPage; 
}
 
public WOComponent deleteAuthor() { 
    // Delete the selected author
    ec().deleteObject(authorItem);
    ec().saveChanges();
    return null; 
    // I prefer to return this.context().page();
    // 'cause it always works, even with complex 
    // embedded components
}
 
// We're gonna need this a lot. 
// Might as well make it easy on ourselves
public EOEditingContext ec() {  
    return session().defaultEditingContext(); 
}

Finaly the relevent code for the AuthorEdit:

public WOComponent backPage;
 
public AuthorEdit(WOContext context) {
    super(context);
    // This be magic!
    // Because the component constructor is called
    // from (Blah)pageWithName("Blah")
    // backPage gets a reference to the calling component
    // Very useful. Don't remember who I learned
    // that from, but thank you!
    backPage = this.context().page();
}
 
public WOComponent saveChanges() { 
    ec().saveChanges();
    return backPage; 
}
 
public WOComponent revert() { 
    ec().revert();
    return backPage; 
}
 
// We're gonna need this a lot. 
// Might as well make it easy on ourselves
public EOEditingContext ec() {  
    return session().defaultEditingContext(); 
}

To my thinking this code is no extra work. It fulfills the requirements of the EOF Commandments and it makes the UI clearer to boot. Most importantly it gives a novice WebObjects developer a strong foundation on which to build. This pattern can be repeated over and over again in their own projects without any risk of invoking the wrath of EOF.

When I get a chance I’ll build this as an Xcode project and post it.

John Simonton – RIP 2005

I am saddened to learn from the Make blog that John Simonton, the founder of PAiA, and probably one of my all-time electronics hobbyist heros, died of cancer sometime last week.

Although I was never able to afford any of his kits as a kid, a friend of mine built the Gnome, and I drooled over the PAiA catalog endlessly. I cannot understate the impact his kits and articles in Popular Electronics had on my electronics career.

create digital music has a good post with plenty of links to commentary and forums.

PAiA has made a resurgence in recent years, I hope his passing will not effect the availability of their kits.

Building a PVR

Inspired by Make, I’ve decided to get my hands dirty again by building a PVR (personal video recorder) for fun. Tivo’s aren’t available in Canada and any of the commercial units are just a tad too "closed" for my liking.

I’m going with MythTV as the core application running on Linux (there is a decent overview of MythTV here). The distro I’ve chosen is the very slick KnoppMyth. It looks like it is definitely the quickest way to get to the end result without having to spend a lot of time hacking Linux (which I have done in the past and would prefer not to have to do again).

Gaining access to Canadian TV listings was a little bit of a concern initially but it looks like MythTV works with a feed from Zap2it Labs.

I downloaded the latest version of KnoppMyth a couple of days ago and the Hauppauge PVR-350 and PVR-500 cards arrived this morning.

The PC I chose is an off-lease Dell GXF240 (P4 1.7 – small desktop). I liked the form-factor, but the card cage may be a little small for the cards (at least the PVR-500). I’m hoping I can get them to fit, but think I’m going to have to do some Dremel work. It was only a couple of hundred bucks, so I don’t mind experimenting.

The 300GB hard drive, DVD burner, and PC should be arriving shortly. Then it’ll be time to get to work.

Second Anniversary

As of today it’s been exactly two years since I started this blog.

Thank you to all of my faithful readers. Without you this milestone would never have been reached. That means you, and you, and you, and… heh?

No, the washroom’s next door.

So is that it? Three? No? Two? Is there anyone else left out in the hall?

Hmm, I think I’ll have to send the invites earlier next year.

Boy we’re gonna have a lot of extra food.

That’s right. Hit the tree.

We’re getting our first snowfall for the year (nothing really significant around here, much worse if you’re north of the city). Anyway, it reminds me of a story.

I was tobogganing with a friend from school. It had been snowing hard and there was a rare (for Toronto) deep layer of white fluffy snow covering everything. The day had that "winter wonderland" quality to it.

The hill we were on was terraced and dipped into a small gully before the main stretch. At the bottom was a field planted with a few (carefully arranged) trees.

We’d taken our run and wiped out in the gully. From there we couldn’t see the bottom of the hill, only the top of some of the larger trees. As we were dusting ourselves off, another kid tore past us, easily making it over the little lip and headed at breakneck speed down the remainder of the hill.

After he’d passed, we heard his sister watching from the top of the hill say – "That’s right. Hit the tree." We turned, there was a short pause, a dull thud, and a FWUMP! as the crown of one of the larger trees shuddered and dropped it’s load of snow.

I think not seeing the actual impact made it that much funnier.