Nested Set Trees in ColdFusion (v0.8)
A common technique to manage hierarchical data in a relational database is to use an "Adjacency List" model, where you have both an ID column and a Parent ID column in a table. This is easy to understand and maintain but can be difficult or inefficient when you want to retrieve hierarchies of records.
The "Nested Set" model provides an alternative technique for managing this kind of data and is more efficient at reading a hierarchy but requires a little more work for inserts, updates, deletes and moves.
This project provides a library of ColdFusion code that you may like to use to help manage your hierarchical data using the nested set model.
Links
This project can be downloaded from RIAForge:
http://nstree.riaforge.org
The documentation for this release:
http://stannard.net.au/blog/page.cfm/nested-set-trees
Changes in this release
This release involved a significant rewrite of the library's structure, but I kept the API as similar as possible.
The main changes include:
- Introduction of a NestedSetTreeTable object, which allows the creation of multiple NestedSetTree objects per table. So the creation of the NestedSetTree objects has now changed.
- Introduction of a Node object to replace the previous struct nodes. So previous code that referred to node.id, node.l and node.r should be changed to use node.getId(), node.getLeft() and node.getRight()
Please add any comments/problems regarding this release to this entry, thanks.

Excellent Work Kevan! :)
I will be trying this out right away with my current flex implementation. I was holding off on certain things simply because I wanted the multi-tree features. I will keep you posted with my experience and hopefully if time allows I'll post a flex example for those interested in the flex side A.S.A.P.
Thanks Again,
Hussein
Love the simplicity and how well genericised the API is!
If you have any further ideas I would be keen hear them.
At this stage I am still considering how the original NestedSetTreeTable object is created. It might be a nice to put all of it's setup in an xml config (similar to how ColdSpring or Transfer is configured). This XML would contain the datasources and definitions of all Nested Set Tree tables in the application. We would probably then have a NestedSetTreeTableFactory to work with, but I am not sure if this just adds unnecessary complexity for most use cases. Anyway I will think about this a bit further.
ON another note, In your query examples I noticed that you use cftransaction and cflocks around your database calls. This might be ok if you are using Access which doesn't support transactions but If you are using MSSQL, ORACLE or MySQL (Innodb) for example, this is an overkill and a potential performance hit under serious load, unless only a very small number of users interact with the category table, then I suppose it wouldn't matter greatly. Just my 2 cents.
1) Transactions to provide a rollback mechanism in the case of a thrown error; such as adding a new node to the tree but the parent is no longer available (perhaps deleted by another user).
2) Locking to ensure the primary table data and NST table remain correctly aligned.
On thinking this though it would appear I don't need the locking on the code that calls the NST functions and the transaction would provide the rollback required if an error was thrown. Is that what you were thinking?
Your 2 cents is of great value :)
Yes, that was part of my thinking. I don't think as long as your database supports its own level of locking that you need to use cflocks around select queries in most cases. I guess some people use it along with cftransaction as a double safety measure, especially on query inserts which to me is more understandable than locking a select query. Truthfully the area of database locking and transactions is really not a straightforward beast to tackle as I am sure you already know. It really all depends.
I would consider that in a site with a heavy load of updates/inserts then the use of this library would perhaps not be recommended - custom built nested set management with stored procedures would be a more efficient approach. However, that being said, I would still like this library to be as efficient as it can be.
I am sure that you are correct that the example usage can be improved. I will put some more thought into them.
Thanks for the feedback.
Here are some of my immediate thoughts of an xml implementation:
XML Config: tree.xml
<nstrees>
<nstree name="ProductCategoryTree" class="shopNutz.com.model.product.ProductCategoryTree">
<datasource>
<property name="NAME" value="shopNutz" />
<property name="USERNAME" value="admin" />
<property name="PASSWORD" value="rubberducky" />
</datasource>
<table>
<property name="NAME" value="categoryNST" />
<property name="TREE_ID" value="1" />
<property name="TREE_ID_COLUMN" value="treeID" />
<property name="ID_COLUMN" value="categoryID" />
<property name="LEFT_COLUMN" value="indexLeft" />
<property name="RIGHT_COLUMN" value="indexRight" />
</table>
</nstree>
</nstrees>
Inside your onApplicationStart() method
//Set up NSTree
<cfset application.nstree = CreateObject('component', 'nstree.NestedSetTreeTableFactory').init('/shopNutz/config/nstree/tree.xml') />
//Set up Product Category Service
<cfset application.productCategory = CreateObject('component', '').init( application.nstree.getTree('ProductCategoryTree') ) />
//Calling the Product Category Service to move a category from within your application controller
<cfset application.productCategory.moveCategory(form.sourceID, form.destinationID) />
Inside your Product Category Service component
//Move A Category Example
<cffunction name="moveCategory" access="public" output="false" returntype="boolean">
<cfargument name="sourceID" type="numeric" required="true" />
<cfargument name="destinationID" type="numeric" required="true" />
<cfset var isMoved = false />
<cfset isMoved = getProductCategoryTree().moveCategory(arguments.sourceID, arguments.destinationID) />
<cfreturn isMoved />
</cffunction>
Thanks,
Hussein
"shopNutz.com.model.product.ProductCategoryService"
//Set up Product Category Service
<cfset application.productCategory = CreateObject('component', 'shopNutz.com.model.product.ProductCategoryService').init( application.nstree.getTree('ProductCategoryTree') ) />
That is what it should be.
Its always worth a look.