By Peter Bell

Fun with Dynamic Programming

Just having fun with a little bit of metaprogramming. Often you want to add a bunch of "me too" methods to your application. For example, you might want to have a bunch of methods called things like getAdminList(), getDefaultList(), getSpecialList(), etc.

You could code them all in full, but that is so 1990s. You could generate the code, but even with active regeneration it is still sitting there, a class of errors just waiting to happen. I though it might be nice to allow for the specification of such "me too" methods as metadata that could then be dynamically processed (can always refactor to code generation later if necessary) . . .

I wanted to be able to describe a method in a single, simple line of metadata such as:

<cfscript>
   set("getAdminList",listToStruct("MethodName:getByFilter|Filter:1=1|Order:Title|PropertyNameList:^"));
</cfscript>

This is saying I want a new method called getAdminList, it should use the actual getByFilter method and should set the Filter, Order and PropertyNameList as described (I use the ^ as my "null" characters to handle the problems with null list elements in ColdFusion). The line uses a method based on the CFLib script by Rob Brooks-Bilson to turn the list into a structure. The method is contained in my BaseMetaData object which beans like PageMetaData and ProductMetaData and UserMetaData extend:

<cffunction name="listToStruct" returntype="struct" hint="I transform a delimited list of property:value pairs into a struct." output="false">
   <cfargument name="List" required="true" type="string" hint="The list to convert to a struct.">
   <cfargument name="ListDelimiter" required="false" default="|" type="string" hint="The delimiter between list elements. Defaults to '|'.">
   <cfargument name="PropertyValueSeparator" required="false" default=":" type="string" hint="The separator between properties and values. Defaults to ':'.">
   <cfscript>
      var ReturnValue = StructNew();
      var Count = 0;
      for (Count=1; Count LTE ListLen(List, ListDelimiter); Count = Count + 1)
      {
          StructInsert(ReturnValue, ListFirst(ListGetAt(list, Count, ListDelimiter), PropertyValueSeparator), ListLast(ListGetAt(list, Count, ListDelimiter), PropertyValueSeparator));
      };
      For (key in ReturnValue)
      // Now remove any string that just has a ^ which is the null placeholder       {
         If (ReturnValue[key] EQ "^")
         {
            ReturnValue[key] = "";
         };
      };
   </cfscript>
   <cfreturn Returnvalue>
</cffunction>

Then at runtime, you would write something like UserService.getByMethod("getAdminList") and it calls the following getByMethod code that does out little bit of magic:

<cffunction name="getByMethod" returntype="any" hint="I return an IBO based on the configuration properties for the requested method e.g. getByMethod('Admin') to get the default admin list. This allows for lots of custom getbyFilter methods without the API growing unreaonably." output="false">
   <cfargument name="MethodName" type="string" required="true" hint="The name of the method to implement.">   
   <cfargument name="OrderOverload" type="string" required="false" default="" hint="The optional order by if you want to overload the default method order based on runtime orderby selections. Using 1..n property names with optional desc after each.">
   <cfscript>
      var Object = new();
      var MethodProperties=Metadata.get(MethodName);
      var Recordset = "";
      If (Len(OrderOverload))
      {
         MethodProperties.Order = OrderOverload;
      };
      // Call the appropriate 'get' method name and pass it the properties all contained in the method metadata       // method metadata sample:       // set("AdminMethod",listToStruct("MethodName:getByFilter|Filter:1=1|Order:Title|PropertyNameList:^"));       Recordset = evaluate("variables.#variables.ObjectName#DAO.#MethodProperties.MethodName#(argumentCollection=MethodProperties)");
      Object.loadQuery(Recordset);
   </cfscript>
   <cfreturn Object>
</cffunction>

I know there are arguments against such approaches, and until I write some more error checking tools, it is a little fragile, but for at the very least a prototyping system it is really allowing me to build code very quickly that is very refactorable.

I also get this isn't exactly rocket science or new, but it made me smile on a Sunday afternoon :->

Any thoughts . . . Is this obvious, boring, completely crazy or just a little bit crazy?!

Comments
I very much appreciate the sentiment, but it made me yearn for a real way to do dynamic methods more than anything.

Incidentally, is there a reason you didn't just use getList("admin") which would then figure out what to do based on what value was passed to it? It seems like a lot of hoops to jump through, but as always, I may have just missed something. =)

I like to play around like this too. Cool idea in any case!
# Posted By Sam | 2/26/07 7:31 AM
Hi Sam,

May be misunderstanding your question. I already have methods like getByFilter(Filter,Order,PropertyNameList) which are dynamic, but sometimes you want to be able to refer to a set of properties by a single name. For instance an admin list for a page might be getByFIlter (Status=1,Title,Title) and I want to be able to set that in my config bean so even without code generation, I can create these "me too" methods without writing them out in full.

The config bean is where I put all my config info for business objects (one per object), the listToStruct is just simply syntactic sugar allowing me to pass a struct into my setter using a list style syntax, and the other details are to allow for runtime overloading of certain properties. For instance, my admin list uses a table with header ordering, so you need the option to overload the OrderBy property if someone has clicked that.

Given that I'm not sure immediately how I'd simplify this, but I'd be really open to ideas :->
# Posted By Peter Bell | 2/26/07 8:10 AM
I think I'm having trouble phrasing my question, actually.

I understood that you have the getByFilter. But I guess what I was thinking is you might have the generic getList method, which would call the getByFilter and take the list type as a parameter.

I guess my thought was that all the lists would differ only in their type (in your example, "admin" would be a type that I'm referring to) so you might just have getList("admin") which would fill in the relevant details in the call to getByFilter.

Does that make any more sense? That's what I'm asking about.

I think the concept is cool. It's sort of like stashing away a function call, from how I understand it. But, is there a need to "stash" it? Like, hmm... does it depend on something else happening before it can be written/compiled (basically?).

Boy, I'm particularly eloquent today, aren't I?
# Posted By Sam | 2/26/07 10:36 AM
OK, so we took this offline to clarify over IM, but if anyone is still confused, just let me know.

The magic line that clarified it for Sam:
Recordset = evaluate("variables.#variables.ObjectName#DAO.#MethodProperties.MethodName#(argumentCollection=MethodProperties)");

Which for (say) a product object and a getByFilter method would evaluate as:
Recordset = variables.ProductDAO.getByFilter(argumentCollection=MethodProperties);


Clearer?!
# Posted By Peter Bell | 2/26/07 10:52 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.005.