<--- --------------------------------------------------------------------------------------- ---- Blog Entry: ColdFusion Iterating Business Objects (IBOs) From The Ground-Up Author: Ben Nadel / Kinky Solutions Link: http://www.bennadel.com/index.cfm?dax=blog:412.view Date Posted: Nov 25, 2006 at 6:08 PM ---- --------------------------------------------------------------------------------------- ---> // Set up the instance object in the private memory scope. VARIABLES.Instance = StructNew(); // This is the index of the current iteration. VARIABLES.Instance.IterationIndex = 1; // These are the list of keys that can be "get"ed and "set"ed in some // way. We are not going to define how they are accessed at this moment. // That will be done once the underlying data structure is set. These will // populate the Get/Set structures (that follow). VARIABLES.Instance.GetAttributesList = "*"; VARIABLES.Instance.SetAttributesList = "*"; // This is a structure of keys that can be "get"ed and "set"ed. These // will be populated based on the attributes list above. VARIABLES.Instance.GetAttributes = StructNew(); VARIABLES.Instance.SetAttributes = StructNew(); // This is a structure that will alias the getter and setter methods. // We are doing this so that we don't have to care about any method // mapping. VARIABLES.Instance.GetAttributeMethods = StructNew(); VARIABLES.Instance.SetAttributeMethods = StructNew(); // This is the underlying data object. I am initializing to an empty qyery. // I can't believe this actually works, but at least it puts a query object // as this variable value. VARIABLES.Instance.RecordSet = QueryNew( "" ); // Set up the non-instance private data. // These are the types of getter/setters that can be accessed/mutated. // Right now, a value can either be accessed as a property or as a // function of the underlying data structure. These values are meant to // be constant and are therefore not part of the instance variable struct. VARIABLES.KeyTypes = StructNew(); // This defines a key as being accessed as a method. VARIABLES.KeyTypes.METHOD = 1; // This defines a key as being accessed as a property. VARIABLES.KeyTypes.PROPERTY = 2; // Set up the default public data. THIS.RecordCount = 0; THIS.CurrentRow = 0; // Define the local scope. var LOCAL = StructNew(); // Duplicate the query when storing it in the IBO. I am not sure that // I completely agree with this, but it seems that this is (in part) // what the original code is doing. We are doing this so that even if // the query is altered after it is sent it, it does not affect the // underlying data of the IBO. VARIABLES.Instance.RecordSet = Duplicate( ARGUMENTS.RecordSet ); // Now that we have the data stored in our IBO, we can populate the // structures of getters and setters. If the list is "*" then we // are going to copy the record set's column list over into the list // of getters and setters. Otherwise, we will keep it as is. // Set local flags for select all. LOCAL.IsGetAll = VARIABLES.Instance.GetAttributesList.Matches( "\*" ); LOCAL.IsSetAll = VARIABLES.Instance.SetAttributesList.Matches( "\*" ); // Check to see if we are getting all properties. if (LOCAL.IsGetAll){ // The Get attributes list will be the record set's column list. VARIABLES.Instance.GetAttributesList = VARIABLES.Instance.RecordSet.ColumnList; } // Check to see if we are setting all properties. if (LOCAL.IsSetAll){ // The Set attributes list will be the record set's column list. VARIABLES.Instance.SetAttributesList = VARIABLES.Instance.RecordSet.ColumnList; } // At this point, we have the get and set attributes list which have // been populated either by the record set OR hard coded by the concrete // extending IBO. Let's store these keys into the get and set attributes // structures for faster lookup and editing. // Loop over get property list. for ( LOCAL.PropertyIndex = 1 ; LOCAL.PropertyIndex LTE ListLen( VARIABLES.Instance.GetAttributesList ) ; LOCAL.PropertyIndex = (LOCAL.PropertyIndex + 1) ){ // Get the current property. LOCAL.Property = ListGetAt( VARIABLES.Instance.GetAttributesList, LOCAL.PropertyIndex ); // Set the property to a default value. VARIABLES.Instance.GetAttributes[ LOCAL.Property ] = 0; } // Loop over set property list. for ( LOCAL.PropertyIndex = 1 ; LOCAL.PropertyIndex LTE ListLen( VARIABLES.Instance.SetAttributesList ) ; LOCAL.PropertyIndex = (LOCAL.PropertyIndex + 1) ){ // Get the current property. LOCAL.Property = ListGetAt( VARIABLES.Instance.SetAttributesList, LOCAL.PropertyIndex ); // Set the property to a default value. VARIABLES.Instance.SetAttributes[ LOCAL.Property ] = 0; } // Now, let's collect the IBO methods that have been mapped to properties. // This is done to allow for differences in naming conventions between the // database (which results in the record set) and the CFC methods. For // instance, the database might have "first_name", but for naming convention's // sake, we can't have a method named "GetFist_Name". To accomodate this, // we are forcing the methods to use the "mapto" attribute. Method's without // this attribute (for both GET and SET) will be ignored. // Loop over the methods to check for anything that begins with "get" or "set". for (LOCAL.CFCProperty in VARIABLES){ // Check to see if this is a user-defined function. We only care // about things that can be called as functions. if ( IsCustomFunction( VARIABLES[ LOCAL.CFCProperty ] ) AND StructKeyExists( GetMetaData( VARIABLES[ LOCAL.CFCProperty ] ), "MapTo" ) AND ( (Left( LOCAL.CFCProperty, 3 ) EQ "GET") OR (Left( LOCAL.CFCProperty, 3 ) EQ "SET") )){ // Get the mapto attribute value. LOCAL.MapTo = GetMetaData( VARIABLES[ LOCAL.CFCProperty ] ).MapTo; // Check to see if this is a get method and that it is in the defined // list of "get"able properties. if ( (Left( LOCAL.CFCProperty, 3 ) EQ "GET") AND ( LOCAL.IsGetAll OR StructKeyExists( VARIABLES.Instance.GetAttributes, GetMetaData( VARIABLES[ LOCAL.CFCProperty ] ).MapTo ) )){ // Flag this property as being accessed as a method. VARIABLES.Instance.GetAttributes[ LOCAL.MapTo ] = VARIABLES.KeyTypes.METHOD; // Set the method alias. VARIABLES.Instance.GetAttributeMethods[ LOCAL.MapTo ] = VARIABLES[ LOCAL.CFCProperty ]; // Check to see if this is a set method and that it is in the defined // list of "set"able properties. } else if ( (Left( LOCAL.CFCProperty, 3 ) EQ "SET") AND ( LOCAL.IsSetAll OR StructKeyExists( VARIABLES.Instance.SetAttributes, GetMetaData( VARIABLES[ LOCAL.CFCProperty ] ).MapTo ) )){ // Flag this property as being accessed as a method. VARIABLES.Instance.SetAttributes[ LOCAL.MapTo ] = VARIABLES.KeyTypes.METHOD; // Set the method alias. VARIABLES.Instance.SetAttributeMethods[ LOCAL.MapTo ] = VARIABLES[ LOCAL.CFCProperty ]; } } } // Convert the record set column list to a structure for faster access. LOCAL.Columns = StructNew(); // Loop over columns and add keys to column struct. for ( LOCAL.ColumnIndex = 1 ; LOCAL.ColumnIndex LTE ListLen( VARIABLES.Instance.RecordSet.ColumnList ) ; LOCAL.ColumnIndex = (LOCAL.ColumnIndex + 1) ){ // Add to struct. The value here is not important. LOCAL.Columns[ ListGetAt( VARIABLES.Instance.RecordSet.ColumnList, LOCAL.ColumnIndex ) ] = 0; } // At this point, we should already have all the mapped methods accounted for. // Now, we have to figure out which of the properties will be accessed as a // property (and which ones are not valid in any access form). To get this, // let's loop over the get and set properties and for each property that is // not being accessed as a method, check to see if there is an equivalent // record set column. // Loop over get attributes. for (LOCAL.Property in VARIABLES.Instance.GetAttributes){ // Check to see if this attribute is zero. This would indicate a // property that has not been validated yet (and is just based on // the list of properties). if (NOT VARIABLES.Instance.GetAttributes[ LOCAL.Property ]){ // This property has not been accounted for. Check for a // matching record set column. if (StructKeyExists( LOCAL.Columns, LOCAL.Property )){ // There is a matching record set column. Set this property as // being accessed as a standard property. VARIABLES.Instance.GetAttributes[ LOCAL.Property ] = VARIABLES.KeyTypes.PROPERTY; } else { // This property has not been accounted for by either the mapped // methods or the record set columns. Delete this property from // the get attributes. StructDelete( VARIABLES.Instance.GetAttributes, LOCAL.Property ); } } } // Loop over set attributes. for (LOCAL.Property in VARIABLES.Instance.SetAttributes){ // Check to see if this attribute is zero. This would indicate a // property that has not been validated yet (and is just based on // the list of properties). if (NOT VARIABLES.Instance.SetAttributes[ LOCAL.Property ]){ // This property has not been accounted for. Check for a // matching record set column. if (StructKeyExists( LOCAL.Columns, LOCAL.Property )){ // There is a matching record set column. Set this property as // being accessed as a standard property. VARIABLES.Instance.SetAttributes[ LOCAL.Property ] = VARIABLES.KeyTypes.PROPERTY; } else { // This property has not been accounted for by either the mapped // methods or the record set columns. Delete this property from // the Set attributes. StructDelete( VARIABLES.Instance.SetAttributes, LOCAL.Property ); } } } // At this point, we have set up the Get and Set attributes based on the // mapped methods and the record set column list. There might be some // differences between the hard coded set/get lists and the available // Set and Get attributes. Not sure how to handle that at the moment. I // am going to leave it as-is for now. However, we could certainly set // the get and let list based on the keys of the attribute structrures: // // For example: // VARIABLES.Instance.GetAttributesList = StructKeyList( VARIABLES.Instance.GetAttributes ); // VARIABLES.Instance.SetAttributesList = StructKeyList( VARIABLES.Instance.SetAttributes ); // Set public data. THIS.RecordCount = VARIABLES.Instance.RecordSet.RecordCount; THIS.CurrentRow = 1; // Now that we have populated the IBO's getter/setter definitions, return // the This reference. We are doing this so that the LoadQuery() method can // be chained with the IBO's constructor. return( THIS ); // Increment the iteration index to point to the next record. At this // point, we don't care about any sort of validation. That is going // to be taken care of in the next step. VARIABLES.Instance.IterationIndex = (VARIABLES.Instance.IterationIndex + 1); // Point the public row to the iteration index. THIS.CurrentRow = VARIABLES.Instance.IterationIndex; // Check to see if the new index points to a valid row. If it does, return true // otherwise, return false. return( VARIABLES.Instance.IterationIndex LTE VARIABLES.Instance.RecordSet.RecordCount );