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:
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:
Then AuthorEdit:
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.