Aliaspooryorik
ColdFusion ORM Book

Using thin wrappers around arrays of structs

I noticed that Sean Corfield had posted on the FW/1 mailing list about using a thin wrapper around arrays of structs. This is something that I've experimented with before and I find it to be a useful technique. Although Sean has taken it to a much higher level than I have, I thought it might be worth sharing an example of what I'm doing.

I use ORM a lot when building applications, but there are times when it can result in an huge amount of SQL statements being run which can affect performance. For example, I've built a site which has Products, those products have Product Options which can have their own prices (imagine a Camera that comes in Black and Solid Gold - they will have different prices).

Originally I used ORM to retrieve a list of all the Products and then called the relationships to find out if they had multiple product options, price variations etc. Whilst this worked nicely, it was far from performant. I ended up replacing the use of ORM for this part (just for this part - not the whole application!!) and replaced it with a single SQL statement.

This is pretty much the SQL I used:


select product_id as id
, product_title as title
, product_imagethumb as thumbnail
, count(option_id) variations
, sum(option_instock) instock
, min(option_cost) as mincost
, max(option_cost) as maxcost
from products
inner join options on option_product_id = product_id
group by product_id
, product_title
, product_imagethumb
order by product_title

It was a bit more complex, but you get the idea

Of course with a query object you can't do any computed values (in Sean's post he uses the example of getAge(), where you want to work out the age based on the data stored in the database). I wanted to be able to have nice methods like hasPriceRange() or hasVariations(), without putting any logic in my views.

At this point I remembered Peter Bell's Iterating Business Object http://ibo.riaforge.org/ and decided to steal the concepts. Which resulted in something like:

ProductIterator.cfc


component {

ProductIterator function init( q ){
variables.recordset = [];
variables.recordcount = 0;
variables.cursor = 0;
if ( StructKeyExists( arguments, "q" ) ){
setQuery( arguments.q );
}
return this;
}

/* Methods for computed values
------------------------------------------------------------------------*/


boolean function hasPriceRange(){
return get( "mincost" ) != get( "maxcost" );
}

boolean function hasVariations(){
return get( "variations" ) >
0;
}


/* Iterator methods
------------------------------------------------------------------------*/


void function setQuery( required query q ){
variables.query = arguments.q;
variables.recordset = [];

for ( var row in variables.query ){
arrayAppend( variables.recordset, row );
}
variables.recordcount = variables.query.recordCount;
}

// returns true if not at the end of the records
boolean function next(){
if ( variables.recordcount > variables.cursor ){
variables.cursor++;
return true;
}else{
return false;
}
}

// returns the value for current cursor position
any function get( key ){
return variables.recordset[ variables.cursor ][ key ];
}

// returns an array of structs, only here for debugging
array function getMemento(){
return variables.recordset;
}

/* Allow for get*key*() style calls without needing to create getters
------------------------------------------------------------------------*/

any function onMissingMethod( missingMethodName, missingMethodArguments ){
var prefix = Left( arguments.missingMethodName, 3 );
if ( "get" == prefix ){
var key = Right(arguments.missingMethodName, len( arguments.missingMethodName ) - 3 );
return get( key );
}
else{
throw "method '#arguments.missingMethodName#' not found";
}
}

}

What it does is to take a query and convert it into an array of structs. I can then loop over the array using the next() method to check if I've got to the end. By utilising onMissingMethod I can keep the syntax of calls such as getID(). For any computed values I simply add my own methods (in this example they are hasPriceRange() and hasVariations().

To use it in your views is simple, here's a complete example:


<cfquery name="products">
select product_id as id
, product_title as title
, product_summary as summary
, product_imagethumb as thumbnail
, count(option_id) variations
, sum(option_instock) instock
, min(option_cost) as mincost
, max(option_cost) as maxcost
from products
inner join options on option_product_id = product_id
group by product_id
, product_title
, product_imagethumb
, product_favoured
, product_group_id
order by title
</cfquery>

<cfset ProductIterator = new ProductIterator( products )>
<!--- start view --->
<cfoutput>
<cfloop condition="#ProductIterator.next()#">
ID: #ProductIterator.getID()#<br>
Title: #ProductIterator.getTitle()#<br>
hasPriceRange: #ProductIterator.hasPriceRange()#<br>
hasVariations: #ProductIterator.hasVariations()#<hr>
</cfloop>
</cfoutput>

It's not nearly as clever as what Sean is doing but it's a useful technique

 


3 comments

  1. Came here to recommend Peter Bell's IBO idea. Was not disappointed. :)

    Comment by Adam Tuttle – February 07, 2013
  2. Haha Adam - I'm good at knowing who to steal ideas from :)

    Comment by John Whish – February 07, 2013
  3. Sweet!

    This not only haves off some overhead but also brings back some of the elegance I love about many patterns used in Java and other languages.

    Comment by Michael Zock – February 07, 2013

Leave a comment

If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)

Please note: If you haven't commented before, then your comments will be moderated before they are displayed.

Please subscribe me to any further comments
 

Search

Wish List

Found something helpful & want to say ’thanks‘? Then visit my Amazon Wish List :)

Categories

Recent Posts