Rebuilding my Base Object
There are quite a few functions in the BaseObject in LightBase that is requires for all of the magic to work, but rather than putting them all into the new version of the code immediately, I'm just going to drop in methods as they are required. To start with I need to be able to initialize, get, set and load properties as well as to have a dump() method (which I use all the time for ad-hoc debugging of my cfscript blocks). I'm also going to add an optional onMissingMethod() implementation so I have the option of calling getWhatever() instead of get("Whatever") to make the system a little more flexible. It'll just be a very simple facade to my generic getters and setters, and later to my "call()" method that allows me to describe methods using paraemeterized method calls which allows me to get rid of most of my Service class, DAO and Controller code.
I've already talked about how the getters and setters should work, and I think the dump() method speaks for itself - providing a cfdump()/cfabort() combo that can be accessed from a cfscript block.
The init method is pretty straightforward - just taking the optional GettablePropertyList and SettablePropertyList (for quick and easy testing both can be defaulted to * to allow anything to be get/set but you probably wouldn't want to develop that way for any period of time except for special cases). If they contain lists, it then creates a struct to make the lookup faster than looking up against a list (I'm not convinced the optimization is required yet, but enough people seemed to be bothered by looking up against a list that I decided just to add the extra few lines of code).
One other method is the load() method. This allows for the quick and easy loading of a bean using a struct. This is really handy as it allows you to tell a Input bean to load all of the elements in a form struct or a URL struct by just writing Input.load(form);. The prefix parameter is used for cases where a structure uses string prefixes. For example if you only want to load Billing information into the bean and you have a BillingAddress1 key, that would get loaded into the bean as Address1, but ShippingAddress1 wouldn't get loaded into the bean at all. I've found this to be really useful in practice.
I also have an asStruct() method which I've found useful for returning all of the gettable properties of an object as a structure just by calling object.asStruct().
Code below. As always, input appreciated. Below is rough first draft code which works but will probably fail for certain edge cases. Next step is to write unit tests (expect a posting tomorrow) and then hopefully I can get into the flow of writing the tests first before the end of the week!
[update] Been doing some more thinking about the asStruct() method as there are a few different intents which could work differently. I'm going to think this through and post somethign up separately. Consider the asStruct() below a placeholder.
<cffunction name="init" output="false" returntype="any" access="public">
<cfargument name="GettablePropertyList" type="string" required="false" default="*" hint="A comma delimited list of instance properties that should be gettable. Defaults to * for all.">
<cfargument name="SettablePropertyList" type="string" required="false" default="*" hint="A comma delimited list of instance properties that should be settable. Defaults to * for all.">
<cfscript>
var i = 0;
// Create structs for more performant processing of gettable/settable property lists variables.GettablePropertyList = arguments.GettablePropertyList;
GettablePropertyStruct = StructNew();
If (len(GettablePropertyList) GT 1) {
For (i = 1; i lte listlen(GettablePropertyList); i = i + 1) {
PropertyName = ListGetAt(GettablePropertyList, i);
GettablePropertyStruct[PropertyName] = 1;
};
};
variables.SettablePropertyList = arguments.SettablePropertyList;
SettablePropertyStruct = StructNew();
If (len(SettablePropertyList) GT 1) {
For (i = 1; i lte listlen(SettablePropertyList); i = i + 1) {
PropertyName = ListGetAt(SettablePropertyList, i);
SettablePropertyStruct[PropertyName] = 1;
};
};
// Create a structure for storing data values within the object variables.InstanceData = StructNew();
</cfscript>
<cfreturn THIS>
</cffunction>
<cffunction name="asStruct" returntype="struct" access="public" output="false" hint="I return the object as a struct.">
<cfscript>
var ReturnStruct = StructNew();
If (len(GettablePropertyList) GT 1) {
For (key in GettablePropertyStruct) {
ReturnStruct[key] = variables.InstanceData[key];
};
return ReturnStruct;
}
Else {
return variables.InstanceData;
};
</cfscript>
</cffunction>
<cffunction name="dump" returntype="void" hint="I provide cfdump/cfabort functionality within cfscript blocks." output="false" access="private">
<cfargument name="VariabletoDump" required="true" type="any" hint="The variable to dump.">
<cfdump var="#VariabletoDump#">
<cfabort>
</cffunction>
<cffunction name="get" returntype="any" output="false" hint="I provide an interface for getting properties from an object." access="public">
<cfargument name="PropertyName" type="string" required="true" hint="The name of the property to retrieve.">
<cfscript>
var ReturnValue = "";
If (GettablePropertyList EQ "*" OR StructKeyExists(GettablePropertyStruct, PropertyName)) {
If (StructKeyExists(variables, "get#PropertyName#")) {
// Custom getter exists - run it ReturnValue = evaluate("variables.get#PropertyName#()");
}
Else {
If (StructKeyExists(variables.InstanceData, PropertyName)) {
// Value exists, return it ReturnValue = variables.InstanceData[PropertyName];
}
Else {
// Value doesn't exist. Throw error. throw("BaseObject: #PropertyName# doesn't exist in the InstanceData structure for this object.");
};
};
}
Else {
// Not gettable. Throw error. throw("BaseObject: #PropertyName# is not a gettable property.");
};
</cfscript>
<cfreturn ReturnValue>
</cffunction>
<cffunction name="load" returntype="void" access="public" output="false" hint="I load an object with the values from a struct.">
<cfargument name="Structure" type="struct" required="true" hint="I am the structure that needs to be loaded.">
<cfargument name="Prefix" type="string" required="false" default="" hint="Optional prefix on all fields being passed in - if this is passed, ONLY set properties with this prefix.">
<cfscript>
var PrefixLength = Len(Prefix);
var KeyWithoutPrefix = "";
For (key in arguments.Structure) {
If (Len(Prefix)) {
If (Left(Key, PrefixLength) EQ Prefix) {
KeyWithoutPrefix = Right(Key, (Len(Key) - PrefixLength));
variables.InstanceData[KeyWithoutPrefix] = arguments.Structure[key];
};
}
Else {
variables.InstanceData[key] = arguments.Structure[key];
};
};
</cfscript>
</cffunction>
<cffunction name="set" returntype="boolean" output="false" hint="I provide an interface for setting the properties of an object." access="public">
<cfargument name="PropertyName" type="string" required="true" hint="The name of the property to retrieve.">
<cfargument name="PropertyValue" type="any" required="true" hint="The value of the property to retrieve.">
<cfscript>
var ReturnValue = false;
If (SettablePropertyList EQ "*" OR StructKeyExists(SettablePropertyStruct, PropertyName))
{
If (StructKeyExists(variables, "set#PropertyName#"))
{
// Custom setter exists - run it ReturnValue = evaluate("variables.set#PropertyName#(PropertyValue)");
}
Else
{
variables.InstanceData[PropertyName] = PropertyValue;
ReturnValue = true;
};
}
Else
{
// Not settable. Throw error. throw("BaseObject: #PropertyName# is not a settable property.");
};
</cfscript>
<cfreturn ReturnValue>
</cffunction>
<cffunction name="onMissingMethod" access="public">
<cfargument name="missingMethodName" type="string" required="true">
<cfargument name="missingMethodArguments" type="struct" required="true">
<cfscript>
var MethodName = "";
If (Left(missingMethodName, 3) EQ "get") {
MethodName = Right(missingMethodName, (len(missingMethodName) - 3));
return get(MethodName);
};
If (Left(missingMethodName, 3) EQ "set") {
MethodName = Right(missingMethodName, (len(missingMethodName) - 3));
return set(MethodName, missingMethodArguments[1]);
};
</cfscript>
</cffunction>
</cfcomponent>



I notice that you store your instance variables in the variables scope.
Are you storing the object in the applcation scope? If so, how are you maintaining data integrity?
If not, then are would be creating a new object for every request? And consequently, how does the application handle under load?
-j
Basically LightWire handles all of the caching of objects so performance isn't an issue and I don't mix page or user specific state into singletons, so there isn't a problem with data integrity.
So right now I probably wouldn't add pagination myself, but if you do give it a try, please let me know how it goes!
Do you have a way of deleting a record from an IBO?
Thanks,
Seth
That said, you absolutely could add methods to an IBO to delete a record, find by property value, find by filter (using QoQ), delete records matching filter criteria, etc. Heck, if you had enough RAM and good enough uptime I guess you could use them to replace the db (or in the real world at least to cache the heck out of it). But it's not a good fit for how I happen to use them.
I wonder what you think of my way of creating the getable and setable information?
http://www.objectiveaction.com/Kevin/index.cfm/200...
I think this is a great approach and it's the direction I'm moving towards in CF as it seems to fit with where CF9 is going in terms of making CF Property the definitive source for metadata. The main practical issue is that you may need to rename some of your metadata for CF9 if you end up using the same names as Centaur uses for some of these things, but quite frankly, a lot of this work is going to be moot in CF 9 as I'm pretty sure there has been public talk about adding ideas like implicit getters and setters, so I think this is mainly going to be a patch for CF8 until CF9 comes out.
I definitely think this is the way to go though.