Snippets: The Base Iterating Business Object
Please see my earlier post for the benefits an iterating business object can provide built up from first principles . . .
The code is pretty simple. The object (I'm going to call it a bean as it will typically expose attributes through getters and/or setters) just needs to be able to load a recordset (I have some methods for loading from a struct and loading a column from a list, but I'm probably going to refactor those into a bean loader called by my bean factory to keep the base bean interface clean). It also needs the minimum iterator method set of first(), isLast() and next() (I'll probably also end up exposing the current record and the total recordcount, but I haven't needed to expose those yet and I like to keep my objects as shy as possible for as long as possible).
The purpose of the bean is to provide encapsulated access to a recordset containing 0..n records, exposing a set of attributes based on those properties using nicely encapsulated getters and/or setters. So, lets start with some code to load up the bean. Please note that the basis of this was a UDF written by Peter Farrell that I got off of CFLib. All it does is to load a query into an array of structures within the variables scope so I have easy programmatic access to the values within the bean by going to variables.Data[RowNumber][PropertyName] (e.g. the title for the 6th article would be variables.Data["6"].Title.
I'm not really sure what the load query should return. When I looked at my initial code I realized it was returning the bean and there was no real reason to do that. It could just have a returntype of void or a simple boolean for success, but for now I've changed it to return the recordcount successfully imported as that could be used for a little double checking or a simple "successfully loaded 12 records" kind of debugging message. I'll see how that does over time.
Note the package level access - this is only available to the same objects service object (or theoretically it's DAO) to keep everything nice and shy.
<cfargument name="Recordset" type="query" required="yes" displayname="Recordset" hint="I am the query that needs to be loaded.">
<cfscript>
// Based on code by Peter J. Farrell (pjf@maestropublishing.com) via cflib.org
var theQuery = arguments.Recordset;
var theStructure = StructNew();
var cols = ListToArray(theQuery.columnlist);
var row = 1;
var thisRow = "";
var col = 1;
If (theQuery.recordcount LT 1)
{
theStructure[row] = variables.setDefaultValues(theQuery.columnlist);
}
Else
{
for(row = 1; row LTE theQuery.recordcount; row = row + 1)
{
thisRow = StructNew();
for(col = 1; col LTE arraylen(cols); col = col + 1)
{
thisRow[cols[col]] = theQuery[cols[col]][row];
}
theStructure[row] = Duplicate(thisRow);
THIS.Recordset = arguments.Recordset;
}
};
variables.Data = theStructure;
variables.NumberofRecords = Row;
</cfscript>
<cfreturn This>
</cffunction>
What else do we need? Just the simple iterator methods of first(), next() and isLast() which manage the variables.IteratorRecord variable which keeps track of the current record. Please note, this is designed for request specific business objects for loading and displaying data. By its very nature it does not need to be thread safe, so the iterator doesn't need to worry about multiple requests playing with its recordcount - not part of the use case.
first()
The first() method just sets the iterator to 1. Should always be called before looping through the iterator just in case the object has been looped through elsewhere within the page (in theory this would never happen, but . . .).
<cfscript>
variables.IteratorRecord = 1;
</cfscript>
</cffunction>
next()
The next() method just increments the iterator so you can work with the next record. If you try to iterate past the last record, it just resets the iterator to point to the last record and returns "false" so you know you've gone too far.
<cfscript>
// Declare a local structure for all local variables
var Local = StructNew();
If (variables.IteratorRecord GTE variables.NumberofRecords)
{
Local.ReturnBoolean = false;
variables.IteratorRecord = variables.NumberofRecords;
}
Else
{
Local.ReturnBoolean = false;
variables.IteratorRecord = variables.IteratorRecord + 1;
};
</cfscript>
<cfreturn Local.ReturnBoolean>
</cffunction>
isLast()
The isLast() method is used as part of the loop controller and returns a boolean. True if this is the last record, false if it isn't.
<cfscript>
// Declare a local structure for all local variables
var Local = StructNew();
If (variables.IteratorRecord EQ variables.NumberofRecords)
Local.ReturnBoolean = true;
Else
Local.ReturnBoolean = false;
</cfscript>
<cfreturn Local.ReturnBoolean>
</cffunction>
init()
Oh, and of course, for the above to work we just need a simple init() method as a pseudo constructor. It actually has a few additional properties, but I'm just showing the stripped down version. I'll show updated versions of the init() code as I show the extra methods in the base bean over the next few posts.
<cfscript>
variables.IteratorRecord = 1;
</cfscript>
<cfreturn This>
</cffunction>
You might be wondering what the display code would look like for (say) displaying a view or a list using such an object. It's actually very simple, but this has been a long post, so I'll post that in a separate article.


Makes plenty of sense. I think this is a better approach (which is why I blog about all of this stuff - so smarter people can show me the error of my ways!).
Many thanks. I'll redo this over the weekend and post the code Monday.
Best Wishes,
Peter