Every day I use ColdSpring I fall more and more in love with it. I am absolutely enraptured with the fact that changing how an application behaves is as simple as changing configuration. Consider this example, which I did yesterday:
I’ve been experimenting with Model-Glue Unity’s generic read and commit messages. In the past I considered them to be handy, but only under certain circumstances. They do have some limitations I don’t like. For instance, if I use soft deletes to flag a record in my database as deleted I don’t want the generic delete to actually try to delete that record. (Besides, what if that record had foreign key constraints that prevented it from being deleted? Ugh.)
Also, when using generic lists, all the records would show up even when they were soft deleted. The easy solution for that was simple enough, simply pass deleted=1 through your URL and add that as a criteria for your list. The thing is, not only does that make the URL that much longer and uglier, users with half a wit could easily view deleted items (but never both deleted and undeleted).
What did I do? Well, I dug into Model-Glues core configuration file, located at /ModelGlue/unity/config/Configuration.xml, which just so happens to be a ColdSpring bean definition file. It didn’t take long to find the block of XML that configured the Reactor ORM adaptor. (I just hit Alt-F and searched for “Reactor”.) This turned up this block of code:
<!-- Reactor adapter/service --> <bean id="ormAdapter.Reactor" class="ModelGlue.unity.orm.ReactorAdapter"> <constructor-arg name="framework"><ref bean="ModelGlue" /></constructor-arg> </bean>
Now, let’s stop and think here for a minute. How does Model-Glue use this adaptor? Well, in a nutshell, it asks ColdSpring for it. ColdSpring configures the specified bean and returns it. Once Model-Glue has its Reactor adaptor it simply calls methods on the adaptor’s public interface.
Specifically, Model-Glue expects an object that implements the same interface as its AbstractORMAdapter. That means we could completely implement the interface defined in the AbstractORMAdaptor or that we could extend the ReactorAdaptor and override a few functions.
For those who don’t know, your ColdSpring.xml file in your Model-Glue application actually overrides settings in the core configuration. This means that to override the configuration all I needed to do was to add an equivalent block into my application’s ColdSpring.xml file.
So, I created my own ReactorAdaptor which looks like this:
<cfcomponent extends="ModelGlue.unity.orm.ReactorAdapter" hint="I am a custom implementation of the MG:U ReactorAdaptor"> <cffunction access="public" name="delete" output="false" returntype="any"> <cfargument name="table" required="true" type="string"/> <cfargument name="primaryKeys" required="true" type="struct"/> <cfset record=read(arguments.table, arguments.primaryKeys) var/> <cfset record.setDeleted(1)/> <cfset record.save()/> </cffunction> <cffunction access="public" name="list" output="false" returntype="any"> <cfargument name="table" required="true" type="string"/> <cfargument name="criteria" required="false" type="struct"/> <cfargument name="orderColumn" required="false" type="string"/> <cfargument default="true" name="orderAscending" required="false" type="boolean"/> <cfargument name="gatewayMethod" required="false" type="string"/> <cfargument name="gatewayBean" required="false" type="string"/> <cfset arguments.criteria.deleted=0/> <cfreturn super.list(argumentCollection=arguments)/> </cffunction> </cfcomponent>
Note that this extends the standard ReactorAdaptor and overrides the delete and list methods to implement the functionality I wanted.
Next I updated my application’s ColdSpring.xml configuration and added this:
<bean id="ormAdapter" class="model.orm.ReactorAdaptor"> <constructor-arg name="framework"><ref bean="ModelGlue" /></constructor-arg> </bean>
Now, as soon as I reloaded my application I was able to use generic list and delete messages with soft deletes.
It doesn’t stop here though. I also extended the core ormController to take care of a few chores automagically for me. For example, I disassemble Reactor Records down to simple event variables (which makes interfacing them with non-scaffolded forms a lot easier) and setup a system to enable multiple generic deletes easily.
How cool is that? Thank you to Joe Rinehart for having this insight about a year before me! Thanks also to the ColdSpring team for making this possible!