IBO 2.0 - Return of the Iterating Business Object. Now on RIAForge!

The Iterating Business Object is the basis of almost everything I do in ColdFusion. The ability to load a recordset (or one or more structs or lists) into a single object and then to get all of the benefits of rich encapsulated getters and setters without any of the downsides just gets better and better the more you use it.

My IBO had gotten a little overstuffed with tangential concerns, but as part of a rewrite of my framework I've cut it down to its core concerns while making some improvements to its functionality (adding class properties) and I have decided to release it as a project on RIAForge - ibo.riaforge.org . . .

At it's core, the IBO is a way of encapsulating the getting and setting of properties for a collection of one or more object instances. It means you can just write a User.getAge() method and as long as User extends BaseIBO, you can use the User object for accessing a single User or (using the iteration functionality) a collection of users loaded one struct at a time or (more often) from a recordset.

But what about Scorpio?
I kind of thought the IBO would change pretty radically under Scorpio with the onMissingMethod() feature. After playing with it for a while, I actually still prefer generic getters and setters. Why? Well because almost everything I do is done dynamically, most of my code is looping through property name lists to display or process given properties. Have you ever tried to call get%PropertyName%() in ColdFusion where %PropertyName% is a variable? It is a pain as there is no built in support for variables["get" & PropertyName]() where the variable clause immediately precedes the method brackets. And honestly, even if that was valid syntax, I find "get(PropertyName)" to be more concise and readable than "variables["get" & PropertyName]()".

Of course if you still write a lot of code by hand it may be much more a matter of preference at to whether you prefer get("Whatever") or getWhatever(), but it is worth thinking about. Even without the generic getters and setters, the iterator, information hiding and distinction between class and instance methods can be pretty useful.

Key Features
The core features of the IBO are:

  • Distinct data store - By storing all properties in a struct within the variables scope (variables.data), you never need to worry about name conflicts between properties and methods again.
  • Generic getters and setters - Never write a "default" getter or setter again. Just use get(PropertyName) and set(PropertyName, PropertyValue). If the get#PropertyName#() or set#PropertyName#() methods exist, they'll be called, otherwise the property value will be returned from a single data store.
  • Information Hiding - With the gettableProperties and settableProperties parameters you can set, you can provide a comma delimited list of which properties you will be able to get or set - allowing for information hiding. (The default of * means that you don't have to use this feature if you don't want to).
  • Class Properties - If you have a collection of users, you probable want the first name and last name properties for each one, but you might also want "class" properties telling you things like the title for the object or the list of properties to display on a given screen. class(ClassPropertyName) and setclass(ClassPropertyName) allow you to get/set such properties at any time meaning you can just pass round a self describing bean to your views rather than having to store so much configuration information in your views or other systems. Information hiding also available for class properties with gettableClassProperties and settableClassProperties. Defaulted to * (all) and "" (none) respectively as you'll usually set these as part of the init method (and they are optional constructor arguments in the base class).

API

  • init - This must be called as if it were a true constructor before calling any other methods on the bean. Optional config properties are GettableProperties, SettableProperties, GettableClassProperties and SettableClassProperties.
  • get(PropertyName: string) - If the property name is not in the GettableProperties list, and the list isn't set to "*" for all, it calls invalidAccess() and (if that doesn't throw or dump) returns an empty string to the caller. If the property name is gettable, if there is a custom getter, it calls it. If not it returns the value directly from Data.Instance[PropertyName] if it exists (if it doesn't exist, it also calls invalidAccess()).
  • set(PropertyName: string, PropertyValue: any) - If the property name is not in the SettableProperties list, and the list isn't set to "*" for all, it calls invalidAccess() and (if that doesn't throw or dump) returns an "false" boolean to the caller. If the property name is settable, if there is a custom setter, it calls it. If not it sets the value directly at Data.Instance[PropertyName].
  • class(PropertyName: string) - If the property name is not in the GettableClassProperties list, and the list isn't set to "*" for all, it calls invalidAccess() and (if that doesn't throw or dump) returns an empty string to the caller. If the property name is gettable, if there is a custom getter, it calls it. If not it returns the value directly from Data.Class[PropertyName] if it exists (if it doesn't exist, it also calls invalidAccess()).
  • setClass(PropertyName: string, PropertyValue: any) - If the property name is not in the SettableClassProperties list, and the list isn't set to "*" for all, it calls invalidAccess() and (if that doesn't throw or dump) returns an "false" boolean to the caller. If the property name is settable, if there is a custom setter, it calls it. If not it sets the value directly at Data.Class[PropertyName].
  • invalidAccess(MethodName: string, PropertyName: string) - This method is called if you try to get or set an instance or class property that is not gettable/settable or doesn't exist. By default it throws an error, but you might want to overload or (what the heck) replace it with your own implementation - including the possibility of just failing silently which I used to do but now dislike. I don't usually recommend changing the core files in a project, but for this project - knock yourself out. It's not like the merge for version 3.0 is going to be that hard to figure out :->
  • loadQuery(Query:query) - This adds a query into the IBO after any existing records and then resets the iterator counter to 1 (pointing at the first record). So if you already have ten records, load a query with twenty and you'll have thirty records.
  • loadStruct(Struct:query) - This loads a structure to add a new object instance to the IBO.
  • blendStruct(Struct:query, override:boolean{default:"false"}) - This blends a structure into the current instance being pointed to. You might use this to loadStruct(form) and then blendStruct(url) to merge the URL and Form scopes into a single IBO.
  • loadList(List: string, ) - This is for the special case where you have a comma delimited list of values and want to load then into n-instances. For instance, StateTitleList="Alabama,California . . ." and StateValueList="AL,CA,...". You would loadList(StateTitleList, "Title") and then loadList(StateValueList, "Value") to load an IBO with the state titles and their values.
  • first() - Sets the instance iterator counter to 1 so you can immediately get or set properties of the first instance.
  • reset() - Sets the instance iterator counter to 0 so you can NOT immediately get or set properties. Why use? Because a common way of looping over an IBO is cfloop condition="#Object.next()#" and to use that structure you must Object.reset() first otherwise you'll never have access to the first record.
  • next() - The core of the iterator. Increments the state by 1 and returns a "false" if this is the last record and a "true" if it isn't. SImple but effective iterator.

Samples

Outputting from an IBO to a table:

<cfset User.reset()>
<cfloop condition="#User.next()#">
   <tr>
   <td>#User.get("FirstName")#</td>
   <td>#User.get("LastName")#</td>
   </tr>
</cfloop>

Alternatives
I've been pushing this for a while, but I'm sure I am not the only person who came up with this idea. I know Paul Marcotte created his own implementation and if there are any other approaches to this, please just add a link to the comments below so people can choose the implementation that works best for them.

Download the bean, get your business objects to extend it, have a play, see what you think. Feedback always appreciated!

Thoughts?

Comments
Puc Covelli's Gravatar Hi Peter, Just wondering how we download this from RiaForge. There appears to be no download link!!
# Posted By Puc Covelli | 6/26/07 9:58 AM
Peter Bell's Gravatar Sorry about that - got a little clean up to do. Will try to get it in tonight!
# Posted By Peter Bell | 6/27/07 9:59 AM
Nick Tong's Gravatar Hey Peter, any idea when you will be releasing the update for this that you mentioned at cfunited europe?
# Posted By Nick Tong | 3/21/08 8:59 AM
Peter Bell's Gravatar Hi Mick,

Good question. I went from CF UNited straight into another big conference. Just got out of that, let me see if I can get this sorted out early next week. Will get it out just as soon as possible, but a little behind on paying proects after two weeks of conferences :-<

Great to see you again!
# Posted By Peter Bell | 3/21/08 9:39 AM
stylo mont blanc's Gravatar ://www.stylomontblancprix.fr/ stylo mont blanc
://www.stylomontblancprix.fr/ mont blanc stylo
://www.stylomontblancprix.fr/ stylo mont blanc prix
://www.stylomontblancprix.fr/ stylos mont blanc
://www.stylomontblancprix.fr/ stylos montblanc
://www.stylomontblancprix.fr/ prix mont blanc stylo
# Posted By stylo mont blanc | 8/10/12 2:35 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.5.006. | Protected by Akismet | Blog with WordPress