Photo from Chile

How I Use Transfer - Part VIII - My Abstract Transfer Decorator Object - Simple Methods

In the past two posts in this series about Transfer (an ORM for ColdFusion) I discussed how I have implemented my Abstract and Concrete Gateways. As you may recall from Part II, that leaves my AbstractTransferDecorator Object as the final abstract object in my model. There is a lot of code in this object, and I plan to provide a lot of explanation, so I'm going to break this up into a few posts.

My AbstractTransferDecorator acts as a base object for all of my Concrete Decorators, and, because most of my Business Objects are created for me by Transfer, it really acts as an Abstract Business Object. For a basic overview of what a decorator is, and how one is used with Transfer, check out this page from the Transfer docs.

I have blogged about this object a few times before, but the posts in this series will, for now, supercede all of those previous posts, as this object continues to change. In fact, this object has a few methods and techniques the design of which I'm not crazy about right now. There will definitely be changes coming, at some point down the road.

I think it's a good idea to start at a high level, so here is a description of the various methods in my AbstractTransferDecorator:

  • configure() - This method is called by Transfer after creating an instance of a Transfer Object. It's basically a decorator's version of the standard Init().
  • save() - This allows the object to save itself.
  • delete() - This allows the object to delete itself.
  • onNewProcessing() - This allows the object to perform actions when a new instance of the object is about to be persisted to the database.
  • populate() - This is used to fill a Transfer Object, and its related objects, with data. It's kind of like a setMemento() method, if you're familiar with that terminology.
  • copyToStruct() - This allows the object to return a simple struct that contains keys for all of its properties. It's kind of like a getMemento() method, if you're familiar with that terminology.
  • validate() - This contains generic validation routines that are shared by all Business Objects.
  • getValidations() - This returns an array of validation rules that apply to the Business Object.
  • addValidation() - This is a helper method to make defining validations simpler.

To keep this post reasonably short, we'll address the first four items, starting with configure():

view plain print about
1<cffunction name="configure" access="private" returntype="void" hint="I am like init() but for decorators">
2        <cfset variables.myInstance = StructNew() />
3        <cfset variables.myInstance.CleanseInput = true />
4    </cffunction>

The Configure method will be called by Transfer whenever a new instance of the object is created. This behaviour is built into Transfer itself. In here I create a struct to hold any private variables that the Business Object may need, and I set a default value of "true" for CleanseInput. CleanseInput is used by the Populate() method to determine whether or not to cleanse HTML from an object's properties prior to persisting it. This is a technique that I'm still kind of on the fence about.

The save() and delete() methods are both short and sweet:

view plain print about
1<cffunction name="save" access="public" output="false" returntype="void" hint="Calls Transfer.save().">
2        <cfset getTransfer().save(this) />
3    </cffunction>
4
5    <cffunction name="delete" access="public" output="false" returntype="void" hint="Calls Transfer.delete().">
6        <cfset getTransfer().delete(this) />
7    </cffunction>

As you can see, save() just calls getTransfer().save(), and delete() just calls getTransfer().delete(). I like this approach as it allows me to minimize references to Transfer in my service layer. I also personally prefer myObject.save() to myService.save(myObject).

onNewProcessing() is called by the service layer prior to saving a new object. Because most objects don't require this functionality this method remains empty in the AbstractDecorator:

view plain print about
1<cffunction name="onNewProcessing" access="public" output="false" returntype="void" hint="Extra processing required for a new record.">
2        <cfargument name="args" type="any" required="true" />
3    </cffunction>

I originally tried to use the BeforeCreate event of Transfer's Event Model for this, but the processing that I wanted to do required data submitted by the user, and the Event Model does not provide a way to pass that in. That is why this method accepts the args argument, which will contain all of the data from the attributes scope (everything the user submitted).

In the next post, I'll walk through the populate() method, which is the largest method in this object.

TweetBacks
Comments
Bob,

Thanks a lot for posting your transfer abstraction.. The effort you have spent thus far helping those of us like myself who could use a solid example of how other people are using transfer... I'm working with MG+transfer, and your series has shed some light on some things I've been toying around with, and overall, this series shouldl help me put the finishing touches on a huge project I've been developing for over a year and a half now.. I thank you again for the examples and assistance!

Please don't forget about the ValueLookupService you mentioned a few posts back.. I have been playing around with my own similar AbstractObjectService while trying to work through the best way to utilize transfer with my projects and and all of these little data lookups I needed to do.. I would love to see how you have implemented this in line with what you've been doing with this series.
# Posted By adam | 7/21/08 4:14 PM
Hi Adam, thanks for the comment. I'm glad that you're finding the posts useful. Please bear in mind that this is just the way that I do it, not necessarily the best or only way to do it. I'm hoping that these posts will be taken as food for thought, rather than as a "how to" guide.

Don't worry, I won't forget about my promise to post about my ValueList Service and Gateway Objects. I wanted to try to get through "the series" first, and there is now some light at the end of the tunnel. I am leaving for 2 weeks vacation in a few days, so those posts won't come until sometime in August, but they will definitely come.

Thanks again for the kind feedback.
# Posted By Bob Silverberg | 7/21/08 5:26 PM
Bob, great series. Question on this Abstract Transfer Decorator. How do you actually code this in the ColdSpring and Transfer configuration files and the actually .cfc files. I'm still confused on how to actually implement this. Thanks.
# Posted By bim | 12/23/08 2:22 PM
It's quite simple. You just create a decorator for your object, and then put the path to your abstract decorator in its "extends" attribute.

The only change required to your transfer config is the addition of the decorator in the object definition (if you're not already using a decorator). For example:

<object name="user" table="tblUser" decorator="model.user">

And then, in user.cfc (your decorator), your cfcomponent tag would look something like this:

<cfcomponent displayname="user" output="false" extends="AbstractTransferDecorator">

And you'd then put all of the code for your abstract decorator into AbstractTransferDecorator.cfc.

None of this has any impact on your Coldspring config. If you want to start injecting dependencies into decorators (which you will, no doubt), at that point you should look at Brian Kotek's bean injector, which does require some Coldspring setup.

I hope that answers your questions.
# Posted By Bob Silverberg | 12/23/08 5:29 PM
Thanks for replying Bob. I tried that and I keep getting the error from Transfer.cfc line 82:
Variable DECORATOR is undefined.

My transfer.xml has the following:
<object name="sys" table="Systems" decorator="cfc.am.common.Sys">
<id name="systemid" type="numeric" />
<property name="systemname" type="string" />
<property name="systemlistenerURI" type="string" />
</object>

My cfc.am.common.Sys has:
<cfcomponent extends="cfc.am.common.AbstractDecorator" output="false">

My abstract decorator has:
<cfcomponent    extends="transfer.com.TransferDecorator" output="false">

The error goes away when I change cfc.am.common.Sys to:
<cfcomponent extends="transfer.com.TransferDecorator" output="false">

It seems as if Transfer doesn't like it when don't immediately extend "transfer.com.TransferDecorator" in my decorator class explicitly.
# Posted By bim | 12/23/08 6:43 PM
Bob, just wanted to follow up on this blog posting. Any ideas from my last comment?
# Posted By Bim Paras | 1/6/09 8:13 PM
Bob, never mind. I found out what the problem was. I had a init() method in my Abstract decorator that was mucking things up. Took that out and everything works!
# Posted By Bim Paras | 1/14/09 2:35 PM