ColdFusion Newbie Contest - Entry 7

Welcome to the 7th entry in the ColdFusion Newbie Contest. The entries just keep getting more and more interesting. I've got about 3 more after this so I'm hoping to wrap this series by next week. Today's entry is from Jonathon Stierman and his coworker Nicholas. I'm not quite sure if it was just Nicholas though.

Take a look at the design. I know I keep saying it - but I'm really shocked at the design level of some of these entries. I know I wasn't doing design this week back in the day. Oh - and I'm not doing it this well now either.

This one can run as a demo - check it out here.

First off - notice on the game display that the top portion, the pie charts, are actually built with ColdFusion's charting. That is one of the most unique uses of charting I have ever seen. Sometimes I forget how powerful and useful the charting is in ColdFusion.

I also found his creature a heck of a lot easier to keep happy. That right there earns him brownie points. (But I'm pretty tired today so I wanted something easy!)

Now that I've praised it is time to complain. First off - his code made use of multiple paths that were hard coded. I counted about three variables that broke down on my machine because my paths did not match his. This isn't the first entry to do this - but the point is critical.

In my case - the situation was a bit more radical. He developed on Windows. I ran it on a Mac. But even if you never have to switch operating systems, it is certainly possible that you might have to move from c:\websites\yoursite to d:\hosts\yoursite.com\wwwroot.

If you had to do this right now - would your code break? It is something to consider.

Like other projects - this one too doesn't do quite enough validation. A good example is in index.cfm:

<cfif isDefined("form.submitted")>
   <!---CREATE THE MONSTER WITH THE NAME ENTERED--->
   <cfset form.imagePath = "stock/creatures/"&form.imagePath />

Note that he doesn't check that form.imagepath actually exists.

I liked how he put his main displays into a folder named displays. It reminds me a lot of Model-Glue views.

Another thing I like is that he documented exactly what each action did to the creatures stats:

<!---
Feed ( hunger++ )
Pet ( affect happiness++ and sanity+ )
Fetch ( affects happiness+++ )
Cuddle ( affect happiness+ and sanity++ )
Groom ( affect sanity+++)
--->

I think maybe one other entry did this. This is very helpful for debugging and just QAing the project in general.

As for his CFC - he uses a CFC similar to an earlier entry - part bean - part generic handler. It is an interesting mix and now that I've seen it twice - it makes me wonder why folks are mixing two concepts like that into one CFC? I'm not saying it is horrible - just interesting.

Oh - and he forgot to var scope at places. I know - folks are tired of me saying that. But I won't keep reminding folks about our friend the var scope.

Ok - download the code folks and share your thoughts please!

Comments

Pretty cool. One thing I noticed was application.cfc needs to be renamed to Application.cfc. Loved the charts!
# Posted By Scott P | 6/1/07 12:13 AM
I hate to beat a dead horse - but I can't understand why people insist on setting variables in the request scope that should be set in the application scope. I know we're only talking about .0001ms probably - but there's no reason to set a var on every request that will not change.

<!--- Provides a single location to update the path to our component(s) --->
<cfset Request.componentPath = "Other.VI-RD.Tamagotchi2.web.Components" />
      
<!--- Useful general information on paths --->
<cfset Request.webRootRel = "/Other/VI-RD/Tamagotchi/web/" />
<cfset Request.webRootAbs = ExpandPath(Request.webRootRel) />
<!--- Path to Creature icons --->
<cfset Request.monsterIcons = "Stock\Icons\" />

Not trying to be harsh - I just don't get it. Maybe it's me...
# Posted By todd sharp | 6/1/07 8:53 AM
Nope, I'm with you Todd (musta missed it when I was reviewing). Of course, maybe I'm scared about what happened last time I brought it up. ;)
# Posted By Raymond Camden | 6/1/07 8:55 AM
Personally, I prefer request scope. I realize that I set the variables every time, but it reads cleaner to me and the performance hit for setting every variable is negligible.

If I used Application scope for every setting, I would have to add a conditional to keep from setting the variables on every request and then change a bit of code every time I added a new variable or else it would never get set.

So, for a miniscule performance my life seems much easier.

Don't see it is a point of major passion though.
# Posted By Steve Bryant | 6/1/07 9:04 AM
Incidentally, that is a really cool use of cfchart!
# Posted By Steve Bryant | 6/1/07 9:06 AM
Steve: That's the beauty of app.cfc though - you don't need the conditional logic like you do in app.cfm. Just set it in onAppStart() and forget it (sorry for the ron popeil reference).

I think people get tripped up when moving from app.cfm logic and forget the benefits of app.cfc.

Also - if you worry about app level vars not being reset - just add a conditional call to onAppStart in onRequestStart like many of Ray's app's do.
# Posted By todd sharp | 6/1/07 9:36 AM
Thanks all for the comments!

I am curious as to how you would approach making those Request (or Application ;))variables (Request.componentPath, etc) not hardcoded. I've secretly hoped there was a way to fully dynamically set up all that information, but I haven't been able to come up with anything that works in all cases.

I'm not an OOP programmer by nature, so I'm hoping to gleam some useful insight out of this :) What do you mean when you say the Creature.cfc is "part bean, part generic"? When we built the "Creature" object, I essentially asked myself "What should a Creature do?" My reaction was to say "It should know some basic information about itself (name, image, birthday, etc), and also perform some actions." Thus all the "action" methods are in Creature CFC along with the getters and some setters for the "basic information." I'm curious as to how else the same functionality could be done.

Thanks all for your comments!

Jonathon
# Posted By Jonathon Stierman | 6/1/07 9:58 AM
No I get the benefit of Application.cfc, but it doesn't really change anything here.

This is evidence by your own quote:
"just add a conditional call to onAppStart"
You still have a conditional, it just seems "better" somehow that the conditional calls out to onApplicationStart, but you are still doing the same thing.

I still need extra code to indicate that I changed the value of a variable or to call that code when I add a new variable.

I'm not saying that no-one should use Application variables for settings (certainly, I do so myself for some conditions), but often (usually) request scoped variables are much more handy for me.
# Posted By Steve Bryant | 6/1/07 10:03 AM
Jonathon,

Take a look at GetBaseTemplatePath() and
GetCurrentTemplatePath() if you haven't already. I generally use getCurrentTemplatePath() from my root Application.cfm/.cfc and then figure the paths for subfolders from there.

You can also use the following code to get the directory delimiter for the OS on which your code is running:
dirdelim = CreateObject("java", "java.io.File").separator
# Posted By Steve Bryant | 6/1/07 10:07 AM
Steve, you misunderstood him. By conditional call he didn't mean:

if app.foo not defined, set stuff

What he was talking about was a way to rerun the onAppStart method by passing a url variable. This is nice during development since onppStart is only run once.

But INSIDE onAppStart, you don't need a condition. That by itself makes it nicer than old school app.cfm code.

Steve (to your second comment) - I _think_ the OS file sep may be in the server scope too. Maybe.
# Posted By Raymond Camden | 6/1/07 10:10 AM
Ray,

Then I didn't make myself clear in my original comment. Having a conditional to reload Application variables was what I was talking about as well. Admittedly, without Application.cfc you need an OR in your conditional to check for prior existence, but I don't think that is a major advantage.

I don't see the location of the conditional as a major factor either. I do see how Application.cfc enforces some organization and that is nice.

My major point, however, is that I don't think that use of request scope or Application scope is a clear issue of one being better than the other. For me, it is personal preference. After all, any performance difference is going to be negligible.

I just checked Server scope and no delimiter.
# Posted By Steve Bryant | 6/1/07 10:27 AM
I've tinkered around with GetBaseTemplatePath() and GetCurrentTemplatePath() (peeked at the Server scope too -- didn't even know that one existed!). Even using those, I'm not sure how I could build my Request.componentPath using those?

So let's say my application is housed in the following directory:

D:\WWWROOT\Other\CowgirlTuff\web\

To build my componentPath, I need some way of chopping off the "D:\WWWROOT\" dynamically. I didn't see anything in the Server scope, or those other two functions that would let me do that... am I missing something?

Thanks for your input thus far (I'm feeling smarter already)!

Jonathon
# Posted By Jonathon Stierman | 6/1/07 10:29 AM
For component path - go relative. If you code tries to make a component of "foo.goo", CF will first look for a foo folder under your CURRENT file. It will then try to find foo in cf mappings. All I did was strip off all your code before the main sub folder.
# Posted By Raymond Camden | 6/1/07 10:31 AM
Jonathon,

Sorry I misunderstood the question. CF will look for your path starting from the current file.

Then it will use mappings. If you don't have a mapping for that path (or a "/" mapping) it will next look from the root of your site and then from the CustomTags folder.

So, I often use "com.sebtools" for my components (because I control "sebtools.com"). When I am on shared hosting, I put the com/sebtools folder in the root of the site. When I control the server (and have multiple sites using those components) I put it in the CustomTags folder. Of course, I never use a "/" mapping, which I think would mess that up (haven't verified that though).
# Posted By Steve Bryant | 6/1/07 10:40 AM
Hmm... I get the order of operations there (local folder first, then CFMappings), but I guess I can't comprehend how to utilize that to get what I need.

Let's assume the following folder structure:
Site Root: D:\WWWROOT\myTestApp\web\
Components: D:\WWWROOT\myTestApp\web\Components\
Example Page: D:\WWWROOT\myTestApp\web\test\test.cfm

Test.cfm needs to call a Component in the Components folder. Is it possible to tell it to relatively "go up a folder, and then into Components/MyComponent.cfc" ? I tried using some "../" notation, but apparently Component Paths can't start or end with a period. :)

I understand how it could work with mappings, but I'd really like to avoid having to set up a mapping for every site on the server :/

"All I did was strip off all your code before the main sub folder."

That sounds really easy, and I feel like I'm missing something terribly obvious!

Jonathon
# Posted By Jonathon Stierman | 6/1/07 10:51 AM
I think some confusion is coming out of our setup on our development machine. We've actually got tons of "sites" running off the same "site" in IIS.

We've got our base folder registered as a site in IIS:
D:\WWWROOT\

But we develop all of our applications in subfolders of that site. For example:
D:\WWWROOT\myTestApp\web\
D:\WWWROOT\myTestApp2\web\
D:\WWWROOT\myTestApp3\web\

If I just cut off the "myTestApp3.web." out of my Components folder, then CF will look in the D:\WWWROOT\ folder for those components (which it won't find, since they don't exist there).

Jonathon
# Posted By Jonathon Stierman | 6/1/07 11:00 AM
Jonathon,

I typically switch my web root to point to whichever site I am currently working on (helps with component paths as well as links and such).

If you are able to do that, it will make your life much easier. If not, then I would suggest calling CreateObject from the root of your site.

Then you can just point to the path to your component from there (as you discovered, no going "up" with component paths).

So, put a UDF in a file in D:\WWWROOT\myTestApp\web\ to call CreateObject (with a path of "Components.componentname") and then call that UDF from test.cfm.

Ben Nadel wrote a great blog entry on his variation on this approach with lots of good information:

http://www.bennadel.com/blog/348-Creating-ColdFusi...
# Posted By Steve Bryant | 6/1/07 11:15 AM
Ahh, cool! I'll dig into Ben's post there.

Unfortunately, I can't be reseting the webroot in IIS. I'm part of a development firm, so I'm not the only one using the website. Can't be switching the webroot on everyone every week ;)


Jonathon
# Posted By Jonathon Stierman | 6/1/07 11:34 AM
Going back to the "As for his CFC - he uses a CFC similar to an earlier entry - part bean - part generic handler." Aspect...

I thought up another way that I might handle this, and would like some input!

Rather than having the "Creature" performing all the actions itself (which now that I think about it, that doesn't quite make sense -- the Creature doesn't "get fed" itself, it needs something "to feed it"). With that in mind, I might have created a CreatureHandler class that actually performs the actions on creatures:

CreatureHandler methods:
pet()
feed()
groom()
play()
etc...

Each function would take in an instance of Creature, and then perform the add/subtracting of nourishment/happiness/sanity.

The one issue I see with this is that then my Creature.hunger, Creature.happiness, and Creature.sanity attributes "public" (at least Getters and Setters) otherwise CreatureHandler won't be able to update the Creature's statistics. That does kind of irk me ... I don't think those attributes should be modifiable outside of Creature (they're "private" to the Creature).

Thoughts?

Jonathon
# Posted By Jonathon | 6/1/07 11:41 AM
I'm not quite sure I'd do it that way. I'd probably have the handler take care of calling the proper methods on the creature. So the create CFC is more simple (can be fed, etc), whereas the handler is more a manager for the CFC.

Really - there isn't one answer for this.
# Posted By Raymond Camden | 6/1/07 2:25 PM