Monday, July 21, 2008
Managing the Transfer Cache with Clones
In my recent adventures with Transfer, I ran into a problem where objects that weren't yet saved were showing up in my cached Transfer objects. Since I don't make mistakes (uh huh...) I immediately assumed this was a bug in Transfer, and went about documenting my problem on the Transfer group.
Well it turns out (surprise surprise) that this was by design, and so I had to make a few changes to my way of thinking in order to get my app to behave the way I expected it to. So here's a rundown of the "problem" and what was done to fix it (thanks to Matt Quackenbush for the fix).
How's the Cache Work?
Basically the Transfer cache keeps track of everything you do to an object, whether it's cached or not. This means that when I create a new object, it's immediately stored in the cache for each retrieval, and if I assign that object to a parent, it's stored with that parent in the cache, even before it gets saved.
My initial reaction to this was that it was bad behavior, but after ruminating over the possibilities, I changed my mind entirely. Being able to store complete transfer objects in cache by default would make it easy to do things like multi-page forms or shopping cart and order entry systems without needing to deal with the caching mechanism for it myself (such as putting an object in session).
The only issue is that if you do this, other parts of the application that can also see that object see non-saved data before you're ready for them to. That's alright if each user has their own unique "thing" that they're dealing with, but it's a big problem if people are sharing objects and data shouldn't be shared until it's validated and saved.
So What's my Problem
I have a Group object, which contains multiple Items. In my application, as part of the process of adding a new Item to a Group, I'm creating an empty Item object, and then assigning it to the right Group before I do anything else. The View then gets this object and the user can manipulate the empty and unsaved object. When the user clicks the Add button, the Item object is validated and then saved to the database if there are no errors. Here's the relevant bit of code that handles this:
<cffunction name="addItem" access="public" returntype="void" output="false">
<cfargument name="Event" type="coldbox.system.beans.requestContext">
<cfset var rc = Event.getCollection() />
<cfset var LOCAL = StructNew() />
<!--- Pass the group ID in in order to assign the item to the proper group --->
<cfset rc.Item = variables.objectFactory.getBean("ItemService").getItem(
rc.GroupID
, 0) />
<!--- Even if a blank object came back, be sure to set the group ID correctly --->
<cfset rc.Item.setParentGroup(
variables.objectFactory.getBean("GroupService").getGroup(
Session.UserID, rc.GroupID)) />
<cfset rc.Success = False />
<cfif IsDefined("rc.do") AND rc.do EQ "additem">
<cfset LOCAL.errors = ArrayNew(1) />
<!--- Populate User with data --->
<cfset getPlugin("beanFactory").populateBean(rc.Item) />
<cfset LOCAL.result = variables.objectFactory.getBean("ItemService").save(rc.Item) />
<cfif LOCAL.result.results>
<cfset getPlugin("messagebox").setMessage(
type="info"
, message="Item Added!") />
<cfset rc.Success = True />
<cfelse>
<cfset getPlugin("messagebox").setMessage(
type="error"
, message= ArrayToList(LOCAL.result.errors, "<br />")) />
</cfif>
</cfif>
<cfset Event.setView("ajax/additem") />
</cffunction>
When I go to my ItemService, grab an Item, and then set it's Parent Group using the setParentGroup method, Transfer updates the Group object in the cache with the new (non-saved) Item. If an error occurs later in the flow during my ItemService.Save method, then the item still stays in the cache, and I end up with an empty Item for every new Item that doesn't pass validation
Cloning to the Rescue
So to fix this problem we can use a clone of an object to do our manipulations before we save it into the database. According to the Transfer ORM documentation, the Clone() method on an object allows you to "... to make a deep clone of every generated TransferObject ...". This newly cloned object is "... outside of any caching that is currently in use within Transfer, which means that any changes that are done to this object, and are not saved, are only particular to the request that they are currently in." In addition to that, the really slick part is "...that Transfer.save() and Transfer.update() on a clone object will update the object currently in cache to the state of the saved object."
Exactly what I need... So with that information in hand, I changed just two lines of code, and the problem was fixed:
<!--- Pass the group ID in in order to assign the item to the proper group --->
<cfset rc.Item = variables.objectFactory.getBean("ItemService").getItem(
rc.GroupID
, 0).Clone() />
<!--- Even if a blank object came back, be sure to set the group ID correctly --->
<cfset rc.Item.setParentGroup(
variables.objectFactory.getBean("GroupService").getGroup(
Session.UserID, rc.GroupID).Clone()) />
You should notice that I chained a Clone() method onto each of the getXXX methods in my services. This means that I get a deep copy of each to perform my operations. So when an error happens, it's happening to the objects in the current request, and not on the objects in the cache. As soon as I call the Service.Save method, validate the object, and then call he Transfer.Save method, all of the cache'd objects get magically updated for me so that they're available through the entire application.
And that's it... So the rule of thumb is, if you don't want your objects updated into the cache until they're saved, always use a Clone'd copy of them.
Posted by Dan | Comments (1) | Add Comment | Permalink
Sunday, July 20, 2008
Dealing with Stamp and StampUpdated with Transfer
Continuing work on my top secret app and learning CCT (ColdBox, ColdSpring, and Transfer), I'm going to describe how to handle stamp and stampupdated columns in your tables.
In every table that I have in any database I build, I have both a stamp and stampupdated column. The stamp column tells me when the record was originally created, and the stampupdated column tells me the last time that data changed. Both of these columns have a default value of getDate(). Here's the colum definition for an example table:
CREATE TABLE [dbo].MyTable( [ID] [int] IDENTITY(1,1) NOT NULL, [Label] [varchar](50) NOT NULL, [stamp] [datetime] NOT NULL CONSTRAINT [DF_Items_stamp] DEFAULT (getdate()), [stampupdated] [datetime] NOT NULL CONSTRAINT [DF_Items_stampupdated] DEFAULT (getdate()),
This works great on initial insert, but doesn't update the stampupdated column on each UPDATE. So to handle making sure the stampupdated gets taken care of properly I create a trigger on each table like so:
CREATE TRIGGER [dbo].[MyTable_Update]
ON [dbo].[MyTable]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for trigger here
UPDATE MyTable
SET stampupdated = getDate()
FROM Inserted
WHERE MyTable.ID = Inserted.ID
END
Now to handle these properly in Transfer, I can't have transfer updating and inserted those values, because the database is responsible for those actions. So let's take a look at what would be the standard property tags for those two columns:
<property name="stamp" type="date" column="stamp" /> <property name="stampupdated" type="date" column="stampupdated" />
So with the above property tags, when I save a transfer object it's going to run either an INSERT or an UPDATE statement that will change the stamp and stampupdated columns. But since the database is responsible for this data, that means that 1) Transfer is doing unnecessary and potentially incorrect work and 2) That the values that the database creates won't get back to the Transfer object. To fix that we need to add a few additional attributes to each of these property tags.
<property name="stamp" type="date" column="stamp" ignore-insert="true" refresh-insert="true" ignore-update="true" /> <property name="stampupdated" type="date" column="stampupdated" ignore-insert="true" refresh-insert="true" ignore-update="true" refresh-update="true" />
I've now told Transfer to ignore the stamp for the purposes of INSERTs and UPDATEs, and to update the Transfer value when a new record is inserted. I don't need to refresh the value on UPDATE, as the stamp value should never change.
On the stampupdated column however, I'm ignoring the value for both INSERTs and UPDATEs, and I'm also refreshing the value on both. This means that on an update the workflow of the UPDATE works something like this:
- Call Transfer save method
- Record is UPDATEd in the database
- On UPDATE trigger fires and updates stampupdated column to the current date
- Transfer queries the update record, picks up the new stampupdated column, and updates the stampupdated property of the object to the new value
- Transfer gives me back a fresh object with the new values
So that's how I handle all of my stamp and stampupdated columns in my database. You can find out more about the Transfer config options at the Transfer ORM docs site.
Posted by Dan | Comments (0) | Add Comment | Permalink
Saturday, July 19, 2008
Creating Snippets in Eclipse
As I'm working with ColdBox, I was reminded just how handy snippets and trigger text can be. ColdBox has a metric ton of snippets available for CFEclipse, most of them with trigger text all ready to go. So I wanted to take a moment to go through how you can create your own snippets and trigger text to speed up common operations
When to use snippets
Sometimes it's just not worth writing a snippet. I could go nuts and create one for every CF function, but in the end I'd spend more time trying to remember my trigger text than I would coding. So building a snippet for a <cfset&cfgt; probably isn't worth your time, but building a snippet for a multi-line piece of code that you write constantly can save you a ton of time. Take the following code as an example:
<tr> <th><label for="MyID">Form Label: </label></th> <td><cfinput type="text" name="MyName" id="MyID" value="#MyObject.getMyName()#" /></td> </tr>
That's a common piece of code to drop into a form for saving an object back to the database, and entering those lines over and over again for each and every property of the object is both time-consuming and error-prone. The perfect job for a snippet.
Retooling the Code
Before actually opening the Snippets view and starting to add a new snippet, I usually take the code I want to create as a snippet, and rework it directly in CFEclipse before copy/pasting it into the Snippet view. The New Snippet dialog is cramped and hard to work in, so this makes the process a little smoother. Here's what my code looks like after refactoring it for use as a Snippet:
<tr>
<th><label for="$${ID}">$${Label}: </label></th>
<td><cfinput
type="$${Type:text|hidden|password|radio|checkbox}"
name="$${Name}"
id="$${ID}"
value="$${Value}" /></td>
</tr>
Notice that I've replaced each of the changeable values of my original code to a Snippet variable. Replace name="MyName" with name="$${Name}" will cause Eclipse to display a dialog for me to fill in the blanks when I insert the snippet. Also, the type Snippet variable is followed by a pipe separate list of prefilled values that I can choose from when I insert this snippet, making it even less error prone and fast.
Creating the Snippet
Now that I have my code ready to go, I can create my snippet. The first thing to do is to make sure you have the Snip Tree View panel available to you in Eclipse. You can enable this view by choose Window > Show View > Other, and then choosing Snip Tree View from the CFML category. After that's complete you should see your Snip Tree View and you can drag it wherever you'd like within the interface.

To keep things organized you click click the box looking icon (Create a new snip package) in the toolbar for the panel to create new folders. I'm going to create a Layout folder and then a Forms folder to hold my new snippet. After creating the new folders, select the Forms folder and click the Plus icon to create a new Snippet.

You can see there's a lot of information here to work with:
- Snippet Name: this will display in the Snippets panel so you can easily find your newly created workflow enhancer
- Trigger Text: the Trigger Text value is a short value that you can use in Eclipse to quickly add the snippet using a shortcut key. I'll cover that at the end.
- Snippet Description: A description of just what this snippet does.
- Snippet start block: If your snippet is designed to wrap around something, this is what would go before the selection in the Eclipse editor window. If your snippet isn't design to wrap around something, this is where you'll put the full text of your snippet.
- Snippet closing block: The closing block of your snippet, if it has one.
- Use this snippet as a file template: You can actually create entire files from a single snippet. I've been using these to create my base Gateways and Service objects so that I don't have to type as much, and I never forget the code that every single one of them needs.
- Template extension: If you're going to create a file template, this is the file extension it'll get
So now that we know all of that information. This is what my newly created snippet is going to look like with the dialog completed:

Using the Snippet
There are two ways to use snippets. The first is by simply finding it in the Snip Tree view and double clicking the snippet. This is how you'll have to do use if you have a snippet that has a start and closing block and you want it to wrap around your current selection. The second way is to use the trigger text (my preferred method).
To use snippet trigger text, simply type the trigger text (in our case inprow) into the Eclipse editor, and with the cursor at the end of the trigger text, press Ctrl/Cmd + J to activate the snippet. Now you should see the snippet dialog with fields for each of the variables you declared:

Now just complete the form and click OK and you have all of your code complete and ready to go:


Hopefully this little tutorial helps you become more efficient with Eclipse. Happy coding!
Posted by Dan | Comments (0) | Add Comment | Permalink
Getting Moving Again...
Things have been rough on the work front lately, but I'm starting to finally move forward again. It's been about 6 months since I've actually built anything worth mentioning, and I'm finally starting to get back into it :)
So today I'm working on a top secret app using ColdBox, ColdSpring, and Transfer, the super trifecta of ColdFusion development (that's my own personal opinion, your mileage may vary) and I'm starting to get the hang of it. I'm doing all of this development locally on my Mac using a VMWare installation of Windows Server 2003 and SQL Server 2005 with Eclipse as my development IDE of choice.
As I move forward, I'm hoping to have the time to post some of my thoughts here on how it all fits together. I've done lots of OO programming over the last two years, but nothing quite like the "pure" MVC that ColdBox and the rest of the frameworks work within. I've generally developed with a View and a Model, but never with the controller in the middle. The new app I'm working on will have external non-CF clients that will need access to the model and a completely different set of views, so I think this is really going to take my development skills to the next level.
So as I move forward, my sincere thanks (already) to Luis Majano and Mark Mandel for all the work they do in both building and supporting their frameworks.
Posted by Dan | Comments (0) | Add Comment | Permalink
Thursday, November 01, 2007
I'm a Godfather
My girlfriend's ex-husband's girlfriend had a brand spankin' new baby today at 6:31. Isabella Johanna Buraglia is 8 pounds, 5 ounces, and 19 1/2 inches long. I'll be booking our trip to The Springer Show soon :).Posted by Dan | Comments (4) | Add Comment | Permalink
Comment from
sumer
on 11/19/2007
Hey that sounds like my life!!! That's awesome, congratulations.
<reply>
Thanks Cuz.
</reply>
Comment from
rbw1
on 1/17/2008
Well, congratulations! I'm a godmother myself - it's a great thing to be
<reply>
:)
</reply>
Comment from
Cornel
on 2/20/2008
Congratualtions to both you and a Angela...
<reply>
Thanks Cornel :)
</reply>
Comment from
sean
on 5/2/2008
Congratulations! Babies are awesome.
<reply>
They're fantastical little creatures...
</reply>
Tuesday, January 09, 2007
Comment from
Eric Meyer
on 1/9/2007
Okay, I give up. What the hell are you talking about?
<reply>
Your browser is obviously not of the same caliber if it can't display the <blink> tag... sheesh...
</reply>
Comment from
Scott
on 1/9/2007
Damn! And just when you thought it was safe to surf the web... ;-)
<reply>
:)
</reply>
Comment from
andrew
on 1/20/2007
pwn list
-----------
[X] Central Texas Regional Mobility Authority
<reply>
lol...
</reply>
Comment from
R P
on 1/22/2007
I am trying to mod a cdrom driver so it will make the the cd's red from the outside in. the file I need to mod is all in bin. at one point it's on all 108 positions in the string. any help decoding and recoding the file when I an finished would be appreachited.
<reply>
um, okay???
</reply>
Comment from
Vhadakhan
on 1/26/2007
ROFL
Comment from
Keukens
on 2/19/2007
But is it a good thing…?
Blinking stuff just freaks me out
Comment from
Scott D
on 3/2/2007
I just wanted to thank you and the Lynda.com staff for all the hard work. I started building a coldfusion site with cartweaver and had the support of you guys to help me along. Also, I know you probably can't say 'don't use this company' but a word to anyone who wants to try and save a buck with hosting companies... don't! I wound up moving everything over to CrystalTech and now things run outstanding. I have since decided to learn more coldfusion and get my hands dirty with code again.
Comment from
Gernot
on 5/10/2007
I know this is a dead post but here's my tribute to the blink tag. http://dbacademy.org It's not pure however, I used css.
<reply>
:)
</reply>
Comment from
Collins
on 7/10/2007
It's been fun using your tutorial on building an ASP web blog. Thanks a million for your wisdom and inspiration
<reply>
You're quite welcome...
</reply>
Thursday, January 04, 2007
"Not of type numeric" when it damn well is....
I have a problem, and it's ColdFusion... We're working on some complex object interaction, and moving data in and out of our objects. Part of the "moving in" part involves building out a structure of arguments based on query columns, and then passing them all in via the ArgumentCollection. Unfortunately, ColdFusion doesn't love us here at lynda.com... It's pitching a fit and saying that our IDs aren't of type numeric, when I know damn well that they are (grumble grumble)...
To demonstrate my point I've come up with the following code example. This fails every time for me:
<!--- Get a query ---<>
<cfset rs = QueryNew("ID,FirstName", "integer,varchar") /<>
<cfset QueryAddRow(rs) /<>
<cfset QuerySetCell(rs, "ID", 3) /<>
<cfset QuerySetCell(rs, "FirstName", "Daniel") /<>
<cfset TestStruct = StructNew() /<>
<cfloop list="#rs.ColumnList#" index="col"<>
<cfset TestStruct[col] = rs[col] /<>
</cfloop<>
<cfdump var="#TestStruct#" /<>
<!--- Pass the newly created struct into the object to run each setter ---<>
<cfset MyObject = CreateObject("component", "cfcs.test").init(ArgumentCollection = TestStruct) /<>
<cfdump var="#MyObject.getSnapShot()#" /<>
That generates the following error:
The argument ID passed to function init() is not of type numeric.
Has anyone else come across this same error? Is there some hotfix that fixes this? It's completely stymied our development... The only way around it is to set our arguments to accept type="any", which honestly is unacceptable...
Posted by Dan | Comments (7) | Add Comment | Permalink
Comment from
charlie griefer
on 1/4/2007
add the [1] inside the loop where you're converting the query to a struct.
otherwise, do some dumps on the rs[col] values. output the value of isSimpleValue(rs[col]) for each loop iteration. they're not simple values.
i'm not sure what they are...i thought omitting the [1] would default to the first row...but something more is going on.
in any event, specifying the record position in the query seems to resolve it.
hope that helps.
<reply>
Yup
</reply>
Comment from
Danilo
on 1/4/2007
Hey man, hope all is well!
My first thought was that the query cell value that is returned is actually some Java data type, not the integer you're telling it to be.
So I tried dumping:
IsNumeric(TestStruct["ID"])
and it output NO so CF wasn't seeing it as numeric. So I thought instead of using the default for pulling the query row value: rs[col] trying rs[col][1] to indicate pulling the first row and then IsNumeric(TestStruct["ID"]) did return YES.
As I don't have access to your CFC...or do I? :-) you'll have to try it to see how it'll work for your situation.
Let me know.
PS: Not that I post here so much, but how about making the textarea a little wider and taller?
<reply>
Nail, meet hammer :)
</reply>
Comment from
Adrian J. Moreno
on 1/5/2007
I'd say the problem is that ArgumentCollection is an attribute of cfinvoke and (without seeing the code for test.cfc) not an argument of the init() method.
I imagine that init() has two arguments:
{cfargument name="ID" type="numeric" required="true" /}
{cfargument name="FirstName" type="string" required="true" /}
Since you're passing in a named argument called ArgumentCollection and no named argument called ID, the error is thrown as expected.
If you instead use
{cfset MyObject = CreateObject("component", "test").init(ID = TestStruct.ID, FirstName = TestStruct.FirstName) /}
the object will be created correctly.
HTH
<reply>
Actually the argumentcollection allows you to pass in an entire struct of arguments without having to name them all :). It came down to me not referencing the proper row of the recordset.
</reply>
Comment from
TAmi
on 1/6/2007
Hi Dan...
I have had the same issue before, but I am surprised you are able to CFDUMP an CFC object. I can never get my CFMX to play nice. I usually have to
then
My CFMX is persnickety about trying to evaluate complex structures that involve CFC calls....
Just a thought...
Tami
<reply>
Interesting issue... not sure what would be going on there...
</reply>
Comment from
DAD
on 1/6/2007
I think that if you turn your = sign over, then take a backward < sign and turn it around to make a >, then take and flip your [ right side up, then leave all the comma's where they are, it ought to work perfectly.
Hope this helps,
Dad
<reply>
Thanks dad, that worked perfectly :).
</reply>
Comment from
Paul
on 1/8/2007
You are using cfmx6 correct? If so this is a known bug and you may be able to get around it by using the javaCast function when setting the id. In general when you create or edit a query in cfmx6 you may have problems when checking the type of the data.
<reply>
Actually it turned out to be me not referencing the column correctly. I needed this:
<cfset TestStruct[col] = rs[col][1] />
</reply>
Comment from
Bryan Ashcraft
on 1/15/2007
Total shot in the dark here, but could be the way you are addressing the value? For instance: arguments.testStruct.id vs arguments.testStruct[1]
<reply>
Yep, you got it Bryan...
</reply>
Sunday, October 29, 2006
Ant, Eclipse, and FTP
I'm having an absolute bear of a time getting Eclipse to play nicely with Subversion, Eclipse, and an Ant build file. One of the wonderful things about working with Dreamweaver is the great FTP integration. I can upload, download, all that wonderful stuff, with a single keyboard shortcut directly in the IDE. I'm not having any such luck with Eclipse.
I think the problem is that I'm not only needing FTP access, I also need Subversion/Subclipse integration. It's my understanding that I can "import" from an FTP site and I forever have a connection through the Team plugins. Unfortunately, I need to "import" from a Subversion repository in order to keep things up to date that way. This makes it damn impossible to use any sort of built in FTP integration inside Eclipse.
To try and get around this I've been playing with Ant (thanks Jared) and build.xml files. The hope is that I can create an Autobuild file that will upload a file any time it's created or changed (I'm not ambitious enough yet to tackle file deletions, bear with me here). Unfortunately, the only thing I've (that means Jared) been able to find is the "depends" attribute of the <target> tag. Unfortunately, this also doesn't seem to work worth a damn... It always uploads the entire project any time a single file changes. The depends attribute isn't checking to see whether anything is newer or not.
The most frustrating part of all of this is trying to find good documentation on complex processes. I've spent several hours on google digging through blog posts, forums, Ant documentation and trying to download jar files, write build scripts, and rebuild workspaces, and nothing seems to work as you'd expect. It seems you need to have a greybeard Java developer looking over your shoulder in order to use Ant, or make it do anything you really want it to.
Can someone prove me wrong, and help me figure out how I can make Eclipse just upload a file when I save it? Is that so much to ask? Please???
Posted by Dan | Comments (3) | Add Comment | Permalink
Comment from
Keith Peters
on 10/29/2006
Sounds like you are off base with depends. That means that one target depends on another target. So targetA is called. It depends on targetB. So targetB is run first. When B is dnoe, then A can run.
<reply>
Gotcha, in that case I got it all wrong :)
</reply>
Comment from
Brooks Andrus
on 10/29/2006
I believe you're trying to use the
http://ant.apache.org/manual/CoreTypes/selectors.html#dependselect
As far as ftp goes, I'd go ahead and use the scp task (Ant docs tell you where to download the dependencies).
I've had no problems only uploading "changed" files using
...Hope this helps :)
Comment from
Emmanuel Okyere
on 10/30/2006
unless i don't really get you, i'll assume what you are looking for are:
svn update (up), svn add, svn commit (ci)
you normally don't want to automate your commits, as you shd normally add meaningful messages per commit
and a depends attribute simply means the listed targets should be executed before the target in context, so i'm not sure how that applies here.
cheers,
-- eokyere :)
<reply>
No, I don't want to automatically commit changes. I want to automatically upload them to a remote server while I'm developing, and then I'll manually Commit files when I'm done on the development server.
Dan
</reply>


Comment from Bob Silverberg on 7/21/2008
Great post, Dan. Based on the recent conversation on the Transfer google group, I was just thinking that we needed a blog post explaining about clone(). I imagine there are many Transfer users who are unaware of its importance.
Just one thing I wanted to add; in your addItem function you're doing a great job of keeping Transfer decoupled from this object by using your ItemService. However, this new change now tightly couples this component with Transfer because you're calling clone() directly. I would consider looking at the getItem() method of your ItemService to see whether you can move the clone() into it.
Oh, and what's up with the ASP blog software? Shame on you ;-)
<reply>
Thanks for the compliment.
True that I've now required my Service to implement a Clone method for my controller to work... Just have to think this through and figure out if there would be times that I wouldn't want a clone back, and should that be an argument to my getXXX methods in my Service layer...
Good catch.
As for the ASP blog, the first post was made in July of 2002 (6 years ago!) and I've never had a reason to change it. It will eventually be converted to CF once I have by CCT chops honed :).
</reply>