Adam Tuttle

Entries Tagged as ColdFusion

Elvis Is (Back) in Production

Remember how I gave Adobe a really hard time about their fumbling of the Elvis operator (among other things) in ColdFusion 11 Update 3?

Today they released Update 4 in the stable update channel. Of course, there were two pre-releases of this patch, so if you were willing to run beta software in production, then you could already be ahead of the game on this one. As a policy, we don't run pre-release platform patches in production.

I wish it would have been released in the stable update channel sooner. The last pre-release refresh was on January 27th. That's 3 weeks and 2 days ago. I think 2 weeks is a pretty good testing period. But, it's been released, and it works —I tested it pretty thoroughly, at least as far as the Elvis operator is concerned. I've already installed it on our production server. So I won't give them too much grief over the timing.

If you're wondering why I cared so much about the Elvis operator, I present to you a sample, in the form of a diff of the awesome terseness that is ?:. These are just a few of the changes that I had sitting in a branch waiting for this day.

What I do think they still could have done better is the updating process, though. It's very evident that Adobe haven't figured out how to use the updater in situations like this.

When I refreshed from the first pre-release to the second, I had to uninstall and re-download the patch. Granted, it was point-and-click through the updater, but... isn't the whole point of having the updater to prevent the tedium of steps like "uninstall, re-download"? I would seriously consider the folks at Adobe read up on semver. The updater should be able to handle all of these situations flawlessly, without the need for manual deletion and re-downloading.

v11.0.4-beta1 is, by semver definition, less than v11.0.4-beta2. And v11.0.4 is by semver definition, greater than both of them. The updater should see all of these version numbers, regardless of which channel was used to install whatever updates are currently installed, and know how to get from point A to point B, with a single "upgrade" button.

Instead, you must:

Re-download

  • Go to the Installed Updates tab, and click the Uninstall button
  • Wait for the service to restart
  • Go to the Settings tab, and click the Restore Default URL button (thank god we got them to add that)
  • Restart the service manually. This isn't listed as a required step, but I always do it because I don't trust them to get it right without a server restart.
  • Log back in, go back to the Server Updates section, Available Updates tab, and click the Re-download button, because apparently the new update looks like the old one to the updater and it thinks you already have the stable release. You don't.
  • After it downloads, click the Install button, and wait for the service to restart

Is this better than the old process? Sure. Is it "good" yet? Nope.

Published 2015-02-19 @ 11:37 in ColdFusion

ColdFusion 11 Member Functions: Where They Got It Wrong

One of the things that Adobe put into ColdFusion 11 that I actually like (*coughs in the general direction of cfclient*) is member functions.

With them, this perfectly functional —if a bit inside out— line of code:

dateTimeFormat( dateAdd( 'd', -5, now() ), 'yyyy-mm-dd' );

... can be rewritten as the much more readable:

now().add( 'd', -5 ).dateTimeFormat( 'yyyy-mm-dd' );

At a glance, I personally enjoy reading the latter one more than the former. It's easier to read left-to right rather than unpacking sets of parenthesis mentally.

But there's still one problem here: .dateTimeFormat() Why not just .format()? Probably because they support all three non-member-function variants: someDate.dateFormat(), someDate.timeFormat() and someDate.dateTimeFormat().

Maybe this doesn't sound completely crazy to you. Fair enough. What if I tell you that, aside from List functions (which get a pass because lists aren't actually a type, they're just strings with some extra parsing rules implied) these three member functions are the only member functions that include their type prefix? Here's the list of all new member functions so you can check for yourself. Now do you think it's an anomaly that's worth looking at?

How can we fix this? By realizing that we don't need all three. The new dateTimeFormat() function added in CF11 uses slightly modified masking from its dateFormat and timeFormat predecessors to allow it to do everything they can do, only better. And since dateTimeFormat() is new in CF11 (just like member functions), it makes a good amount of sense to rip this band-aid off now, while we still can. If we wait any longer, someDate.dateFormat() is going to end up in a lot of existing codebases, and we'll never be rid of it, because of backwards compatibility.

As Scott Stroz is fond of saying, "If only the meeting had lasted 5 more minutes..."

Some have called for deprecation of the dateFormat and timeFormat member functions. I disagree, but also think it's better than nothing. My opinion is that member functions are still new enough that it's ok to say, "Sorry, we screwed this up. Let's fix it before we make it any worse."

This has been logged as a bug: 3940802. Your votes and comments would be appreciated.

Published 2015-02-16 @ 05:07 in ColdFusion

Save Elvis! er, I mean ?:

Today was an interesting day.

I got an alert from the Adobe Bug Tracker, as I often do, to let me know that there was a change to one of the bugs I created / voted for / commented on. I clicked through to see what bug it was and what was new. It was a new comment on this beauty (CF11u3 broke the ?: operator) from everyone's favorite CFML curmudgeon (I think?), Adam Cameron. He asked what the status was for releasing the fix.

"A valid question!" I thought to myself, considering the bug was marked fixed on December 22nd, 2014. What gives, Adobe? Where's the fix? So I tweeted it, too. I believe strongly that the prescribed methods of communication should be used, until you start getting ignored or otherwise let down. Then, if the cause is just, it's time for a small dose of public shaming:

A few tweets were had back and forth between myself, Elishia, and Anit, with the end result being that the fix is scheduled for the next regularly scheduled update. Sounds great, right? Except updates are quarterly. Who wants to wait 3 months for a regression to be fixed? Is that even reasonable for a commercial platform? I don't think so, personally. Here's what I wrote in a comment on the bug, following that conversation:

I've already stated this elsewhere but just to put it on the record: I think that waiting for a quarterly update to fix a regression Adobe caused is terrible.

You say, "If you need a fix sooner, please contact our support team to request a private fix for this."

This implies that you have or can fairly easily create an update that fixes the issue. Why not just release that as update 4 and push all of the other non-update3-regressions back to update 5 on its normal schedule?

Further, "If you need a fix" can be rewritten as, "If you're using this feature" and I can assure you that either people have rolled back to update 2 or removed the feature from their code, because having syntax errors in your code is not just something you can leave be while you wait 3 months for a fix.

Either one is a losing proposition: There were things that are very nice to have included in Update 3 (many, many things, if my memory is correct. And if so, good and thank you for the update!) so rolling back to Update 2 denies us of useful features and bug fixes JUST like staying on Update 3 would deny us of this feature.

And lest you think I'm just being a jerk-nose for the sake of jerk-nosing...

Alas, that was the last they seem to have to say on the matter. Unacceptable, in my opinion. They did, however, offer to provide a "private fix" (their words) for anyone that asked.

For my part, while I do want it fixed, I don't consider myself special. I don't have a support contract — aside, you know, from the fact that CF11 was just released and is still in core support. So I'm not requesting a "private fix." But that doesn't mean you shouldn't.

In fact, I had the idea that we should more or less flood them with (legitimate) requests for the "private fix" in hopes that, if we can get enough, it will be easier for them just to go ahead and release an out-of-band patch. Also known as "doing the right thing."

So I made a form for you. Just click on over here, enter your name and email address, and submit the form. They'll be emailed, and you and I will be copied. (I keep a copy for posterity. Don't want any funny business with fudged numbers...)

Please feel free to share the link, and please only use the form once. It does not make any attempt to prevent duplicate submissions, and in my estimation they'll do more harm than good, so let's just keep things on the up and up, ok?

Published 2015-01-21 @ 04:34 in ColdFusion

In Response to CFHour #226

The latest episode of the CFHour podcast mentioned me in two consecutive segments, and in the latter they said that they fully expected me to respond... And who am I to let down a podcast to which I regularly listen?

First, they very kindly mention that I wrote a book! Thanks for that mention, guys. If you had reached out to let me know you were going to mention it, I would have been more than happy to offer a discount code for your listeners. Alas, I'll have to give that out here instead. CFHour listeners can save 10% by using this link.

The second segment was about my previous blog post, detailing the reasons that we gave up attempting to switch to Railo.

ORM Mapped Superclass entities named the same

As I mentioned in my previous post:

using MappedSuperClass with ORM entities that have the same name (e.g. User and base/User) — for reasons I don't feel like elaborating on but I assure you are completely legitimate — causes a Stack Overflow error

I guess Scott didn't accept my assurances of legitimacy. Not that I blame him, I'm always a skeptic, myself...

Scott: I guess I kind of understood, but I would never think to do that. Like, I would probably name the base user cfc, baseUser.cfc
Dave: Mmmhmm
Scott: I've used MappedSuperClasses a lot in the stuff that I do. As a matter of fact, all of my ORM entities extend a BaseEntity that has common methods that I want available to each one of the entities.
Dave: Yeah, most of mine do too.
Scott: I would just never think to name them the same name. Or more to the point, I wouldn't think to not name them base{Blah}, as part of the actual name itself—
Dave: Yeah
Scott: —rather than—
Dave: Uh-huh
Scott: —using a path to delineate them.

Ok guys, here goes. We've got lots of entities, and this system is only fractionally complete. We're sitting on around 50 entities right now and can easily see that expanding to be several hundred. In addition to that, this is for a resellable product, not a one-install bespoke system. So we're writing with customer-customizations in mind. Our customers are universities and colleges across the country (and maybe eventually the world?) and I'm sure you realize that they all have their own favorite way of storing data and their own favorite custom fields; so we're planning ahead to support that sort of thing.

Maybe User was a poor example. Universities tend to refer to their students, alumni, and sometimes even faculty and staff, as Constituents, so let's go with that. Uni A wants an isFaculty flag, an isAlumni flag, an isParent flag, and so-on (not a terrible idea, considering it's possible to be all 3 of those, and more)... Meanwhile Uni B might prefer to just have a type_code column with coded values to represent every possible case. Neither is objectively wrong, and it would be a crappy reason to lose a potential customer if we couldn't support their desired data formats.

So how did this drive the naming decision? Well, with potentially several hundred entities on our hands, our first goal was to set things up so that customizing the entities for one customer didn't make it difficult to maintain the source code (remember, this is all going into git) for other customers. So the strategy we decided on was to put the system properties in the MappedSuperClass orm/base/Constituent.cfc, and the custom properties into the child class orm/Constituent.cfc. Each customer will have their own branch of the repo, so the child class will stay empty in the master branch and the customer modifications to it will be done only in the customer's branch. This should (in theory!) practically eliminate merge conflicts (at least in the case of ORM entities), and keep each branch pretty clean.

We wanted to make maintenance as easy as possible, too. I suppose baseConstituent.cfc is just as easy to remember as base/Constituent.cfc — no argument there. But what happens when an entity legitimately needs to start with the word "Base?" Sure, we can have BaseBaseSalary.cfc (Yes, that's completely contrived: Why would you need a Base Salary entity? I've no idea!) but that just has a certain smell to it that I'm not fond of... like milk that hasn't quite expired yet, but still makes you think twice before drinking it. So instead we stuffed them in a base/ folder and called it a day.

As you've probably figured out, this was something that we were able to deal with relatively quickly: It wasn't "the last straw," so it had already been resolved by the time that last straw was reached. I do have to say, though, that I was very happy to revert the commit that made this work on Railo and go back to my base/Constituent.cfc strategy.

CFImport vs. App-Specific Mappings

I think we're mostly of the same mind on this, so just one quick note here: I think that the only way I'll consider this functionality "fixed" is when mappings are respected in all cases on all platforms. Anything short of that is a cop-out for what amounts to "academic" reasons. (If you're asking yourself what constitutes "academic" reasoning, think about your ipod playing the same song 5 times in a row in shuffle mode even though you'd hate that, because that's how random numbers work sometimes... Academic reasoning is not always the best.) And I don't think CF11 has, as you mentioned Scott, "fixed" (removed) the include-based workaround. I'm pretty sure it still works fine for me on CF11, though I did not revert the code changes I made to get this functionality running on Railo.

Metadata

Scott: This is the one that really had me kind of scratching my head

No worries, I'll explain!

Your description of the problem is dead-on: We've got functions with a definition like this:

function remove( rc ) iq:role="users:delete" {}

In fact, nearly every Controller method looks like that. It's the configuration necessary for our role-based security implementation. We combine this metadata with FW/1's before() lifecycle event to authorize (nearly) every pageview / form submission / etc a user makes in the application. This makes the code very terse, which is a particular passion of mine, but does come with its own challenges. For one, getting that metadata is on the "expensive" (in computational time) side. Again, we have to make use of a base class, but in this case it's just a generic baseController that all controllers extend:

component {

    function init(){
        var md = getMetadata( this );

        for ( var fn in md.functions ){
            if ( structKeyExists( fn, "IQ:ROLE" ) ){
                variables.permissions[ fn.name ] = fn['IQ:ROLE'];
            }
        }
    }

    function before( rc ){
        //if the requested action requires user to be logged in, verify that they are
        verifyLoggedIn( rc.action );

        //if user is logged in, check role-based security for the requested action
        verifyPermissions( framework.getItem() );
    }

    function verifyPermissions( 
        methodName
        ,failMessage = 'You do not have access to the requested functionality'
        ,jailEvent = 'main:jail/home'
    ){
        //action doesn't have role requirement
        if ( !structKeyExists( variables.permissions, methodName ) ){
            return true;
        }

        var requiredRole = variables.permissions[ arguments.methodName ];
        if ( session.user.isAssignedRole( trim(requiredRole) ) ){
            return true;
        }

        request.context.requiredRole = requiredRole;

        framework.redirect( arguments.jailEvent, "requiredRole" );
    }

}

When each controller is initialized (infrequently) it grabs a copy of its own metadata (see init()) and stores a copy of the iq:role metadata in the component instance (variables scope) for later reference. Then, during the before() lifecycle event, it is referenced to make sure the logged in user has access to the requested action, before the action happens.

Apologies, that was a rather long-winded way of getting to this next part: Why is the metadata named iq:role? Why not iq_role? The short answer is: Because when you can, it makes sense to... And you can on Adobe CF10+... So I did.

In a few more words: Namespacing. You never know what crazy feature the platform vendor will come out with next, right? I mean... CFClient, anyone? So by appending custom metadata to your functions, you run the risk that one day they will release a new version that you really want to use, but now they have revitalized <cflogin> to actually be relevant, and oh by the way, it uses a role metadata attribute on your functions. So you have to go change every function that uses your custom role metadata attribute if you're not lucky enough to have been using it "their way" before they were. By prepending some characters that are unique to your application, you reduce the risk of a collision later down the line. Just to be clear, the extra characters don't change the functionality in any way, they just reduce that risk of collision. That's what namespacing is for, but it still doesn't fully explain why ours uses a colon instead of an underscore or something else.

And the answer to that question is: because I said so! When you're in charge of decisions like that (and I am, right now), you sometimes get to pick things because you like the way they look. On Adobe ColdFusion, iq_role and iq:role are functionally identical, so I picked the one that I found more visually appealing. I can't really explain why I find the colon more pleasing to the eye, but I do. I probably saw it somewhere else (Java? Python? Who knows...) and it stuck with me. And it continues to serve us well on the ACF platform.

Taffy users are also probably familiar with it, as Taffy supports both taffy_uri and taffy:uri metadata attributes for specifying a resource's uri mapping (for both Railo and ACF8-9 support reasons).

So why doesn't Railo support it? Because they also support this: role:"foo". At some point in the past they chose to accept a colon as the delineator between the LHS and RHS of attributes, and as a consequence they can not support colons in the LHS. For them, it is like trying to write: iq=role="foo" — and I accept that. That is a particularly hard problem to solve, and I don't blame them for not considering it a high priority. I find it unfortunate that they dug that hole for themselves, but there's no use dwelling on the past: This is the world we live in, so this is what we have to deal with. Doing otherwise would be like fighting gravity.


Scott: You know, when people run into these problems and I don't, I can't help but think, "Are they doing something really weird, or am I doing something really weird?" Are they doing it wrong, or am I doing it wrong? And you know, when you're talking about me and Adam Tuttle, I would err on the side of me doing it wrong.

That's very kind of you to say so, Scott, but I think that this time it's very clearly my fault that it doesn't work on Railo... I didn't originally make the choice with platform agnosticism in mind. But would I go so far as to say it is "wrong"? I think not.


One last thing about that previous post ("We Tried (and Failed) to Switch to Railo")... While it was not meant to be putting down Railo in any way, I think it would be easy to see it in that light. I actually really think Railo is a great option; even more so if you get to start with a clean slate. But I just wanted to put out some information that showed a first-hand account that switching from one platform to the other is not all unicorns and rainbows. There is significant work involved. So when the fanboys spout off on mailing lists that you should just switch, you can rightfully start ignoring them. It's not that simple.

Scott had mentioned toward the end of that segment that he was impressed that these were the only issues that we ran into, and there is some truth to that statement, but probably not the kind he intended. It's more like the truth that your keys are always in the last place you look for them because you stop looking when you find them. We gave up once we ran into the metadata issue. It's possible that could have been the last thing; but it's equally possible that there could have been 30 other differences to deal with once the metadata problem was resolved.

In truth, we were working at a running pace. Steve was about to leave for a road trip and I was head-down on something unrelated, and while we were switching to a new AWS EC2 instance, we thought it would be a good opportunity to give Railo a try. We didn't really have the time to deal with a lot of issues that needed sorting out, so our threshold for conversion pain was pretty low. It ended up being about a 2 hour experiment that proved it wasn't going to be quick and painless to switch. Would we make these changes down the road and try again? We might... When we have time available to deal with it.

Published 2015-01-19 @ 09:40 in ColdFusion