Snippets: A Generic Getter
I love dynamic programming patterns. All other things being equal, the less code in your application, the eaiser it is to code, debug and maintain. Thoughtfully applied dynamic programming can drastically cut the amount of code in your application, making it much more maintainable and flexible. Imagine having a single generic base getter for all of your objects that would support information hiding, remove the need to test for existance, and that would seamlessly support overloading by custom getters where required. That's what I'm playing with here and it seems (so far) to be working very well . . .
Lets start by looking at the design decisions, and then I'll share the (fairly trivial) code.
Firstly, I wanted to make sure that my generic getter wouldn't just share all of the properties loaded into my iterating business objects, so I added a private variables.gettableAttributeList() property to my base bean with a comma delimited list of the attributes I wanted to share. It does support a * to share all of the internal properties, but that is more for quick prototyping than for an actual system and I'll probably remove that feature as I start generating the business objects again. If an attribute name isn't on that list for the particular object called, the generic getter won't even try to return it.
Secondly I didn't want to get ColdFusion errors just because someone called for an attribute that didn't exist. I'm not sure how to handle this for the long term, but right now the method can either return a null string or an informative error message if you get an attribute that doesn't exist. The null string is very useful for the Input facade I use to wrap form and URL variables as I can always just call Input.get('whatever') and just test for length. Of course, it removes the small amount of information that distinguished between a variable not existing in the scope and it having a null length, but that is a price I'm willing to pay to avoid all of the If IsDefined("input.whatever") code I used to have to write.
Thirdly, I wanted to support custom getters that would automatically overload the base method but only for certain attributes. If there was a custom getter for User.get('Age'), I wanted to use that, but if there wasn't one for User.get('FirstName') I wanted to just use the generic method.
Fourthly I wanted to make sure that only my bean needed to know whether or not there was a custom getter for a given property. If I have to call User.getAge() (for the custom method) and User.get('FirstName') (for the generic one) I'm exposing more details of my beans internals than I really should.
So, how does it work? Have a look at the code and I think you'll see it is pretty obvious . . .
<cfargument name="AttributeName" type="string" required="yes" displayname="get" hint="The name of the attribute to get">
<cfset var Local = StructNew()>
<cftry>
<cfscript>
If (ListFindNoCase(variables.gettableAttributes,arguments.AttributeName) OR variables.gettableAttributes EQ "*")
{
// The attribute is "gettable" (either because it is in the list or because all attributes are gettable (*))
if (structKeyExists(variables,"get#arguments.AttributeName#"))
{
// If there is a custom getter, use it
Local.ReturnValue = evaluate("variables.get#arguments.AttributeName#()");
}
Else
{
// Otherwise just pull the attribute using private "hard get"
Local.ReturnValue = hardGet(arguments.AttributeName);
};
}
Else
{
// The attribute is not "gettable"
If (variables.ReturnNullforNonExistantAttributes)
{Local.ReturnValue = "INVALID_ATTRIBUTENAME(#arguments.AttributeName#)";}
Else
{Local.ReturnValue = "";};
};
</cfscript>
<cfcatch>
<cfif variables.ReturnNullforNonExistantAttributes>
<cfset Local.ReturnValue = "">
<cfelse>
<cfset Local.ReturnValue = "NON_EXISTANT_ATTRIBUTENAME(#arguments.AttributeName#)">
</cfif>
</cfcatch>
</cftry>
<cfreturn Local.ReturnValue>
</cffunction>
Firstly it checks to see if it is allowed to get the attribute or not (thus removing the biggest issue I usually have with generic getters that just let ALL of their properties hang out!). If so, it looks to see if there is a custom get#AttributeName#() method. If so, it calls that and returns that value. If not, it uses a special "hardget()" function. This is responsible for pulling the value of a given property from the array of structures it is stored in for the current iterator record. It encapsulates knowledge of the actual data structure within the variables scope and is often used by the custom getters (I'll show one of those in the next post) which need to actually get property values and then operate on them to return a particular attribute (e.g. hard getting a Date of Birth to return a users calculated Age).
You also see that if the attribute isn't gettable or the method (or its sub method) fails, it either returns a null value or a meaningful string. I'm not happy with how this works, but as I'm not sure what problems I'll need to solve, I'll just leave this alone until it becomes a problem and will refactor it from there.
I have found this a boon in prototyping applications, but even in a live app, it removes the need for "dumb" getters, supports information hiding and still allows for seamless addition of custom getters where business rules become necessary. I certainly couldn't have finished my last two projects in the timeframe I did without this and the iterating business object I've been talking about for the last couple of weeks.



<cfinvoke method="#Variables.stValidate[arguments.theProp][Arguments.valNum].data#" value="#Arguments.theVal#" returnvariable="status">
Its a long variable in the method attribute so I apologize for the complication but I just have a function called addValidation() that adds 1 or more validation rules per object property so its a struct of arrays. The .data property is the 4th parameter of the addValidation() function which in this case is the name of the custom function I want to be calld during validation. So its really doing this in my base object (the isRegistered function is in my user object);
<cfinvoke method="isRegistered" value="#Arguments.theVal#" returnvariable="status">
Great way to get around not having to use evaluate(). I'll be implementing some of the things you have in this function tomorrow. Thanks again Peter. Its starting to make much more sense now. Take it easy.
Glad you like it! I'm still not convinced that evaluate is as bad as people suggest - I'll do some performance testing some time to check it out. Also, as you'll see, I'm using cfscript which doesn't allow for cfinvoke without creating a seperate method or function call. I did look at cfinvoke and understand why others would use it. Some time one of us will have to sit down and performance test the cfinvoke vs. the evaluate. I definitely don't think evaluate should be used where there is a simple alternative (evaluate(form.#FieldName#) can be replaced quite easily with form[FieldName]), but I'm still not sold that evaluate is evil. In fact, when I get a chance I'll do some testing. Look out for a "Evaluate() Rules" post some time soon :->
Just read your article on Encapsulating recordsets and wanted to let you know what a great read I found it to be. I was just wondering if you have the code from the article posted anywhere? Again, great article!
Cool - glad it is out. I will post the code tonight or tomorrow - with some updates to reflect newest thinking.
<a href=http://www.tbcgold.com>World of warcraft gold</a>
<a href=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...>wow gold</a>
<a href=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...>buy wow gold</a>
<a href=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...>cheap wow gold</a>
<a href=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...>buy cheap wow gold</a>
<a href=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...>gold wow</a>
<a href=http://www.tbcgold.com/dofus-gold-10.html>dofus kamas</a>
<a href=http://www.tbcgold.com/dofus-gold-10.html>kamas dofus</a>
<a href=http://www.tbcgold.com/dofus-gold-10.html>dofus achat kamas</a>
<a href=http://www.tbcgold.com/dofus-gold-10.html>acheter kamas dofus</a>
<a href=http://www.tbcgold.com/dofus-gold-10.html>dofus kamas gratuit</a>
<a href=http://www.tbcgold.com/dofus-gold-10.html>achat dofus kamas</a>
<a href=http://www.dofushq.com>dofus kamas</a>
<a href=http://www.dofushq.com>dofus gold</a>
<a href=http://www.wowgold-de.com>wow gold</a>
<a href=http://www.wowgold-de.com>wow gold kaufen</a>
[URL=http://www.tbcgold.com]World of warcraft gold[/URL]
[URL=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...]wow gold[/URL]
[URL=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...]buy wow gold[/URL]
[URL=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...]cheap wow gold[/URL]
[URL=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...]buy cheap wow gold[/URL]
[URL=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...]gold wow[/URL]
[url=http://www.tbcgold.com/dofus-gold-10.html]dofus kamas[/url]
[url=http://www.tbcgold.com/dofus-gold-10.html]kamas dofus[/url]
[url=http://www.tbcgold.com/dofus-gold-10.html]dofus achat kamas[/url]
[url=http://www.tbcgold.com/dofus-gold-10.html]acheter kamas dofus[/url]
[url=http://www.tbcgold.com/dofus-gold-10.html]dofus kamas gratuit[/url]
[url=http://www.tbcgold.com/dofus-gold-10.html]achat dofus kamas[/url]
[URL=http://www.dofushq.com]dofus kamas[/URL]
[URL=http://www.dofushq.com]dofus gold[/URL]
[URL=http://www.wowgold-de.com]wow gold[/URL]
[URL=http://www.wowgold-de.com]wow gold kaufen[/URL]
<a href=http://www.srogold.com/rs2-gold-1.html" target="_blank">http://www.srogold.com/rs2-gold-1.html>runescape gold</a>
<a href=http://www.srogold.com>cheap wow gold</a>
<a href=http://www.srogold.com/the-lord-of-the-ring-us-gold-29.html>lotro gold</a>
<a href=http://www.srogold.com/ever-quest-2-gold-1257.html" target="_blank">http://www.srogold.com/ever-quest-2-gold-1257.html...>eq2 gold</a>
<a href=http://www.srogold.com/maple-story-eu-gold-173.html" target="_blank">http://www.srogold.com/maple-story-eu-gold-173.htm...>maplestory mesos</a>
<a href=http://www.dofusmax.com>dofus gold</a>
<a href=http://www.srogold.com/silkroad-online-gold-27.html>silkroad gold</a>
<a href=http://www.srogold.com/rs2-gold-1.html" target="_blank">http://www.srogold.com/rs2-gold-1.html>runescape money</a>
<a href=http://www.srogold.com>wow gold</a>
<a href=http://www.srogold.com/the-lord-of-the-ring-us-gold-29.html>lotr gold</a>
<a href=http://www.srogold.com/maple-story-us-gold-20.html>maple story mesos</a>
<a href=http://www.dofusmax.com>dofus kamas</a>
<a href=http://www.srogold.com/rs2-gold-1.html" target="_blank">http://www.srogold.com/rs2-gold-1.html>runescape account</a>
<a href=http://www.srogold.com>buy wow gold</a>
<a href=http://www.dofusmax.com>kamas dofus</a>
<a href=http://www.srogold.com>buy cheap wow gold</a>
<a href=http://www.dofusmax.com>dofus achat kamas</a>
<a href=http://www.srogold.com>world of warcraft gold</a>
<a href=http://www.dofusmax.com>acheter kamas dofus</a>
<a href=http://www.srogold.com>gold wow</a>
<a href=http://www.dofusmax.com>dofus kamas gratuit</a>
<a href=http://www.srogold.com/fly-for-fun-gold-1330.html>flyff penya</a>