Tuesday, February 26, 2008

I still miss CFOUTPUT

It's been about a year now since I last coded CF in anger, and what coding I've done since then has been mainly in Java and Ruby On Rails. These days, most of my coding is done in my spare time, which is a resource in increasingly short supply - so coding actually tends to be done during my random bouts of insomnia. Like many similar turncoats, I've found RoR development to be settling into a fairly steady cycle :



  1. Do some really complicated stuff really quickly.

  2. Nod appreciatively and make coo-ing noises.

  3. Stop yourself just before standing up and announcing that your name is Boris and you are invincible

  4. Try to do something ostensibly simple, get stuck

  5. Spend ages scouring the web for the simple solution that surely must be out there somewhere

  6. Get pissed off because you found a blog post telling you that you shouldn't want to do that because it's "not the rails way"

  7. Figure out a really ugly cumbersome way of doing it step-by-step

  8. Fail to get to sleep because it's just bugging you that something so simple should be so hard in a framework that makes so many other more complicated things so simple

  9. Two days later, discover entirely by accident that the fifty lines of hackery could have been done in one long line of twenty method calls ( things.each do{ |foo| foo.do.some.other.thing }.and.then.do.some.thing.else( bar ) ) if only you'd known that the method you needed was called (insert counter-intuitive method name here) and that there was a plugin called (insert name of obscure plugin here)

  10. Go back and redo your hack with the one long line of twenty method calls. Feel like a l33t h4x0r d00d because you just replaced fifty lines with one.

  11. Discover that somewhere in your one long line of twenty method calls, one of them is returning nil.

  12. Think "hey, it's ok, I can debug this easily with the console!". Feel smug.

  13. Spend what seems like an aeon starting the console, reproducing the conditions in which you get your nil, changing something, then restarting the console so you can test your change.

  14. Resolve to write better unit tests in future.

  15. Pine a little for the good old days of "make your change then hit f5" to see if something works.

  16. Start a trawl through the source code looking for the cause of the problem

  17. Reflect that while duck typing is indeed an orgy of sheer loveliness, in this particular case it would be nice if just this once you could know for sure that this particular object is a Foo and therefore all you need to know would be found in foo.rb

  18. Discover that the cause of your woe is that at least one of your magical plugins doesn't work on Oracle, or SQL Server, or indeed anything other than MySQL.

  19. Go back to the blog on which you found the plugin to see if it's a known problem with a new version

  20. Discover that the blog is down.

  21. Write a plugin to patch the plugin to work on Oracle

  22. Feel vaguely uneasy that you now have a chain of umpteen plugins patching plugins patching plugins patching plugins patching ActiveRecord to do something that you apparently shouldn't want to do, but dammit, you needed to get it done by 5pm.

  23. Go out and pull scary faces at small children to make them cry for a while until you feel better.

  24. Come back feeling much better now that you've spread a little frustration around. Reflect that you're probably just not thinking about things in the "right" way.

  25. Adjust your thought-angle, and come at it again

  26. Repeat from step 1


To be clear, I do think that Ruby has some wonderful features. Blocks, open classes, method_missing - all of these little niceties make some fantastically cool things possible. The Enumerable#sort_by method in particular was one discovery that just gave me a wonderful warm fuzzy feeling - e.g.

some_collection.sort_by { |element| [ element.method1, element.method2, element.method3 ] }

Rails, also, has some really great features, and makes some of the donkey work so easy it's almost laughable. But sometimes it feels like all the thinking went into the elegance of the back-end design, and not enough thought went into the templating. RHTML feels like a tacked-on afterthought. HAML is better in some respects, but it still feels awkward. I haven't yet found any templating language that even comes close to the sheer simplicity and ease-of-use of CFOUTPUT.



The example that triggered this rant was grouped output. Let's keep this simple for the purpose of example - say I have a recordset with three fields:

























































Type Sub-type Title
Type 1 Sub-type 1 Foo 1
Type 1 Sub-type 1 Foo 2
Type 1 Sub-type 1 Foo 3
Type 1 Sub-type 2 Bar 1
Type 1 Sub-type 2 Bar 2
Type 1 Sub-type 2 Bar 3
Type 2 Sub-type 3 Foobar 1
Type 2 Sub-type 3 Foobar 2
Type 2 Sub-type 4 Foobar 3


- etc etc.



If you wanted to output these with headers and sub-headers whenever the type or sub-type changed, it would be almost trivially easy in CF:



<cfoutput query="myRecordset" group="type">
<h2>#myRecordset.type#</h2>
<cfoutput group="subtype">
<h3>#myRecordset.subtype#</h3>
<ul>
<cfoutput>
<li>#myRecordset.title#</li>
</cfoutput>
</ul>
</cfoutput>
</cfoutput>


- but in Rails? I'm still at step 5. I know I'm not the first person to need to do this, not by a long shot, but I just don't yet know what the method that surely must exist would be called. I know you can use Enumerable#group_by to group an array of
objects into something approximating the raw recordset above, but that's kind of working backwards to me.

I also know that someone will probably post the answer in a comment, probably with some kind of dismissive one-word instruction like "Read." or "Learn." linking to the relevant part of the docs. And that's all well and good. I'm just in a temporary bout of misty-eyed nostalgia for the things that CF made easy - particularly CFOUTPUT.

12 comments:

Tom Mollerus said...

I agree, being able to group with CFOUTPUT is a major advantage for CFML over PHP (I can't speak for Ruby, though). In PHP, you have to create your own variables to track which group you're in, and write your own conditional code to react when the group changes. It's messier and far less elegant.

DEfusion said...

Surely CF is doing something similar to this pseudo code in the background

curType = null
for each record in myRecordSet
    if curType NEQ record.type
        [content]
    end
    curType = record.type
end

You could probably do something similar quite easily with a proc in ruby if you want to nest it.

Alistair Davidson said...

defusion :
Yep, agreed - I just wondered if this time I could shortcut steps 5-9 :-)

DEfusion said...

Well I took it as a challenge after leaving that comment and I've just knocked this together http://pastie.caboo.se/157667.

Not as short and sweet to use as cfoutput (and I don't like the x.first in the output loops), also I'm sure that the logic definition could be tidied up and it will probably (make that will) fail in some cases.

Alistair Davidson said...

defusion: very cool, nice one

Mike said...

Sadly, I'm actually enjoying your pain. Sorry about that, I know it's not nice.

Glad you found a solution at least!

Alistair Davidson said...

Mike:

Hey, I'm glad someone's enjoying it! It's not really that painful anyway, I just figured that with the whole world and his hamster seemingly jumping on the Rails bandwagon, I should give a little shout out to CF for having got something "right" ten years ago :-)

Jake said...

By the way, CF8 now has onMissingMethod(). I read about this in Fusion Authority Quarterly Update (by Sean Corfield), and I liked this quote: "ColdFusion 8 moves our favorite language into the Smalltalk/Ruby camp by adding a small feature: the ability to define a function called 'onMissingMethod' inside any CFC. ColdFusion is now more OOP than Java."

DEfusion said...

Yeah sometimes "The Rails Way" is not right and when you come across those circumstances it is usually a pain to circumvent.

I'm thinking of calling stored procedures as a key example, sometimes it's just magnitudes more faster and efficient to have something complex performed within a stored procedure.

Alistair Davidson said...

defusion : absolutely, and sometimes in the enterprise space, you may have to deal with an existing database where you *only* have permissions to call stored procs.

rip747 said...

When in doubt, watch a railscast

http://media.railscasts.com/videos/029_group_by.mov

rip747 said...

Oh and by the way. Two things that have prevented me from moving to RoR for all development needs that maybe you know.

Would you mind reading my blog post about it and commenting there?

My Blog