Dynamically injecting data into an Object

ColdFusion, ColdFusion 8, ColdFusion 9
I like to write as little code as possible (that’s the whole point of coding, right?) and recently, since I started working with ORM, have found the need to inject data into an object. Often with an ORM object -- though this isn’t limited just to ORM -- you create a new object then have lines of setXXX functions like so:
 
s = entityNew( 'Sample' );
s.setAge( form.age );
s.setFirstname( form.firstname );
 
Well I got pretty bored of writing all those setXXX lines. So I wrote a function to do it. (This is not the only way or necessarily the best way but I thought I would put it out there).
 
<cffunction name="injectInto">
<cfargument name="obj" required="true" hint="I am an object for injecting">
<cfargument name="st" required="true" hint="I am a structure of data.">
<cfloop collection="#arguments.st#" item="local.key">
            <cfif structKeyExists( obj, "set#local.key#" )>
                        <cfinvoke component="#arguments.obj#" method="set#local.key#" >
                        <cfinvokeargument name="#local.key#" value="#arguments.st[ local.key ]#">
                        </cfinvoke>
            </cfif>
</cfloop>
</cffunction>
 
First off this is all in tags as there is no invoke function in cfscript. Originally the if structKeyExists line was a try and catch. That worked but felt a little like overkill so I experimented around and found that structKeyExists works as I blogged about. I also tried using getComponentData which returns all functions but again this felt heavy to me.
 
So, what does the function do? It takes in an object that you want injected and a structure of data to inject into it, loops over that data, checks to see if the object publicly wants it and if so uses cfinvoke to give it to the object. The above code now becomes:
 
s = entityNew( 'Sample' );
s injectInto( s, form );
 
But what if you have data in multiple structures; form, url, request for instance? 
 
<cffunction name="injectInto">
<cfargument name="obj" required="true" hint="I am an object for injecting">
<cfloop from="1" to="#structCount( arguments )#" index="local.collection" >
            <cfif isStruct( arguments[ local.collection ] )>
                        <cfloop collection="#arguments[ local.collection ]#" item="local.key">
                                    <cfif structKeyExists( obj, "set#local.key#" )>
                                                <cfinvoke component="#arguments.obj#" method="set#local.key#" >
                                                <cfinvokeargument name="#local.key#" value="#arguments[ local.collection ][ local.key ]#">
                                                </cfinvoke>
                                    </cfif>
                        </cfloop>
            </cfif>
</cfloop>
</cffunction>
 
The above function will take in any number of structures and inject their data into the object. You may notice that the function only has one argument even though I just said it can have an endless number of arguments. That’s because I am going to programatically deal with them.
 
Regardless of how many arguments a ColdFusion function has defined it will always take in everything you give it. (Its generous like that! ;) ) If the arguments are not named then they take a number in the arguments structure. Let’s call the above function and then dump its arguments passed into injectInto.
 
//setting up some “fake” structures for form, url and request
f = { age="23" };
u = { firstname="Sam", age="21" };
r= { userID=2134132 };
sObj = new Sample();
injectInto( sObj, f, u, r );
 
 
As you can see the only named argument is the first one passed in – obj, the others get numbers for names. This is useful as it preserves the order they are passed and means we can loop over them and inject their data into the object which going back to the injectInto function is exactly what happens.
Henry Ho said:
 
would this work in cfscript?

var setter = arguments.object["set#local.key#"];
setter(arguments.st[local.key]);

?
 
posted 83 days ago
Add Comment Reply to: this comment OR this thread
 
Brad Wood said:
 
@Henry: When I tried that approach in CF8 it did not work. I believe it is because you are placing the pointer to the setter in your locally varred scope and when you run it, it does not execute in the context of the component any longer. Instead it simply modifies the page you are on and the component remains unchanged.
 
posted 83 days ago
Add Comment Reply to: this comment OR this thread
 
Brad Wood said:
 
If you really want to keep it all script you can ditch cfinvoke and use evaluate.
evaluate("arguments.obj.set#local.key#(arguments[ local.collection ][ local.key ])");

And if you didn't want to use evaluate, you could get really fancy and do this:
arguments.obj._tempSetter = arguments.obj["set#local.key#"];
arguments.obj._tempSetter(arguments[ local.collection ][ local.key ]);
structDelete(arguments.obj,"_tempSetter");

And who says ColdFusion doesn't give you options! :)
 
posted 83 days ago
Add Comment Reply to: this comment OR this thread
 
samfarmer said:
 
@Brad: Cool!
 
posted 83 days ago
Add Comment Reply to: this comment OR this thread
 
Henry said:
 
Let's post this to CFLib.org and Adobe CF Cookbook!
 
posted 83 days ago
Add Comment Reply to: this comment OR this thread
 
Brian Swartzfager said:
 
Very interesting idea here, Sam. But given that the example involves inject data provided by a form or URL variables (user-provided data), I'm curious as to how you would go about validating the data and ensuring it's safe. Would you do that before injecting the data, or would your setter functions within the object take care of that step?

If the former, perhaps you could use a two-step injection process: you inject the values from the form/URL scope into a validation object that returns a struct of valid/clean values, then take that struct and inject it into your actual data object.
 
posted 83 days ago
Add Comment Reply to: this comment OR this thread
 
samfarmer said:
 
I like the idea of the object doing the validation and like the look of Dan Vega's HyRule: http://hyrule.riaforge.org/

There is also Bob Silverberg's ValidateThis ( http://www.validatethis.org/ ) framework and I'm sure a few more that escape me right now.
 
posted 83 days ago
Add Comment Reply to: this comment OR this thread
 
Ben Nadel said:
 
It's it just awesome how crazy flexible ColdFusion is?!?
 
posted 83 days ago
Add Comment Reply to: this comment OR this thread
 
 
This was the approach I was referring to in my last comment. I took notice of it during one of Bob Silververg's presentations, and have made a habit of using it for my init constructor code ever since. It's nice to be able to do something like:

o = createobject("component", "obj").init(argumentCollection = struct);

I've also had the init() method act as a wrapper for a public setAll() method which essentially does the same thing. That way later I can call setAll() and it sort of makes sense what's going on.

Code-wise, I've been doing mine using try/catch blocks to eliminate errors for when the setter does not exist. If I did want there to be some error handling, I would have the catch block throw the error with a custom message. For the most part, I don't want to know if a setter method failed, only if a particular setter didn't get called when it was supposed to. Typically that sort of debugging doesn't happen until after the fact. I think I'm going to go with the StructKeyExists approach from now on. Good post.
 
posted 82 days ago
Add Comment Reply to: this comment OR this thread
 
sam said:
 
very cool i found your post just in time too
 
posted 16 days ago
Add Comment Reply to: this comment OR this thread
 

Search

Twitter
You should follow me on Twitter here
About Me
I am a 34-year old Web Developer specializing in ColdFusion. I live and work in downtown Washington, DC with my wife and two daughters. Read more About Me

2007 CFeMmy Best Newcommer winner
As voted on by fellow CF Bloggers.