Showing posts with label CFML. Show all posts
Showing posts with label CFML. Show all posts

Sunday 17 April 2022

A day in the life of trying to write a blog article in the CFML ecosystem

G'day:

This is not the article I intended to write today. That article was gonna be titled "CFML: Adding a LogBox logger to a CFWheels app via dependency injection", but I'll need to get to that another day now.

Here's how far that article got before the wheels fell off:

And that was it.

Why? Well I started by writing an integration test just to check that box install logbox did what I expected:

import test.BaseSpec
import logbox.system.logging.LogBox

component extends=BaseSpec {

    function run() {
        describe("Tests the LogBox install", () => {
            it("is instantiable", () => {
                expect(() => getComponentMetadata("LogBox")).notToThrow()
            })
        })
    }
}

Simple enough. It'll throws an exception if LogBox ain't there, and I'm expecting that. It's a dumb test but it's a reasonable first step to build on.

I run the test:

Err… come again? I ain't installed it yet. I lifted the code from the expect callback out and run it "raw" in the body ofthe test case: predictable exception. I put it back in the callback. Test passes. I change the matcher to be toThrow. Test still passed. So this code both throws and exception and doesn't throw an exception. This is pleasingly Schrödingeresque, but not helpful.

The weird thing is I know this is not a bug in TestBox, cos we use notToThrow in our tests at work. I port the test over to my work codebase: test fails (remember: this is what I want ATM, we're still at the "red" of "red-green-refactor").

I noticed that we were running a slightly different version of Testbox in the work codebase: 4.4.0-snapshot compared to my 4.5.0+5. Maybe there's been a regression. I changed my TestBox version in box.json and - without thinking things through - went box install again (not just box install testbox which is all I really needed to do), and was greeted with this:

That's reasonably bemusing as I had just used box install fw1 to install it in the first place, and that went fine. And I have not touched it since. I checked what version I already had installed (in framework/box.json), and it claims 4.3.0. So… ForgeBox… I beg to differ pal. You found this version y/day, why can't you find it today? I check on ForgeBox, and for 4.x I see versions 4.0.0, 4.1.0, 4.2.0, 4.5.0-SNAPSHOT. OK, so granted: no 4.3.0. Except that's what it installed for me yesterday. Maybe 4.3.0 has issues and got taken down in the last 24h (doubtful, but hey), so I blow away my /framework directory, and remove the entry from box.json, and box install fw1 again. This is interesting:

root@280d80cf28c6:/var/www# box install fw1
√ | Installing package [forgebox:fw1]
|------------------------------------------------
| Verifying package 'fw1' in forgebox, please wait...
| Installing version [4.2.0].

4.2.0. But its entry in its own box.json is 4.3.0, and the constraint it put in my box.json is ^4.3.0.

I do not have time or inclination for any of this, so I just stick a constraint of ~4.2.0 in my box.json, and that seems to have solved it. I mean the error went away: it's still installing 4.3.0. Even with a hard-coded 4.2.0 it's still installing 4.3.0.

Brad Wood from Ortus/CommandBox had a look at this, nutted-out that there was something wrong with the way the FW/1 package on ForgeBox was configured, and he in turn pinged Steve Neiland who looks after FW/1 these days, and he got this sorted. I'm now on 4.3.0, and it says it's 4.2.0. And box install no longer complains at me. Cheers fellas.

Then I noticed that because of the stupid way CFWheels "organises" itself in the file system, I have inadvertantly overwritten a bunch of my own CFWheels files. Sigh. CFWheels doesn't bother to package itself up as "app" (its stuff) and "implementation" (my code that uses their app), it just has "here's some files: some you should change (anything outside the wheels subdirectory), some you probably shouldn't (the stuff in the wheels subdirectory)", but there's no differentiation when it comes to installation: all the files are deployed. Overwriting all the user-space files with their original defaults. Sorry but this is just dumbarsey. Hurrah for source control and small commit iterations is all I can say, as I could just revert some files and I was all good.

Right so now I have the same version of TestBox installed here as in our app at work (remember how this was all I was tring to do? Update testbox. Nothing to do with FW/1, and nothing to do with CFWheels. But there's an hour gone cocking around with that lot).

And the test still doesn't work. Ballocks.

I notice the Lucee version is also different. We're locked into an older version of Lucee at work due to bugs and incompats in newer versions that we're still waiting on to be fixed, so the work app is running 5.3.7.47, and I am on 5.3.8.206. Surely it's not that? I rolled my app's Lucee version back to 5.3.7.47 and the test started failing (correctly). OK, so it's a Lucee issue.

I spent about an hour messing around doing a binary search of Lucee container versions until I identified the last version that wasn't broken (5.3.8.3) and the next version - a big jump here - 5.3.8.42 that was broken. I looked at a diff of the code but nothing leapt out at me. This was slightly daft as I had no idea what I was looking for, so that was probably half an hour of time looking at Lucee's source code in an aimless fashion. I actually saw the change that was the problem, but didn't clock that that is what caused it at the time.

Having drawn a blank, I slapped my forehead, called myself a dick, and went back to the code in TestBox that was behaving differently. That would obviously tell me where to look for the issue.

I tracked the problem down to here, in system/Assertion.cfc:

    // Message+Detail regex must not match
    if (
        len( arguments.regex ) AND
        (
            !arrayLen( reMatchNoCase( arguments.regex, e.message ) ) OR !arrayLen(
                reMatchNoCase( arguments.regex, e.detail )
            )
        )
    ) {
        return this;
    }

    fail( arguments.message );
}

I distilled that down into a portable repro case:

s = ""
pattern = ".*"
writeDump([
    reMatch = s.reMatchNoCase(pattern),
    matches = s.matches(pattern),
    split = s.split(pattern)
])

There are some Java method calls there to act as controls, but on Lucee's current version, we get this:

And on earlier versions it's this:

(Full disclosure: I'm using Lucee 4.5 on trycf.com for that second dump, but it's the same results in earlier versions of Lucee 5, up to the point where it starts going wrong)

Note how previously a regex match of .* matches an empty string? This is correct. It does. In all regex engines I know of. Yet in Lucee's current versions, it returns a completely empty array. This indicates no match, and it's wrong. Simple as that. So there's the bug.

I was pointed in the direction of an existing issue for this: LDEV-3703. Depsite being a regression they know they caused, Lucee have decided to only fix it in 6.x. Not the version they actually broke. Less than ideal, but so be it.

There were a coupla of Regex issues dealt with between those Lucee versions I mentioned before. Here's a Jira search for "fixversion >= 5.3.8.4 and fixversion < 5.3.8.42 and text ~ regex". I couldn't be arsed tracking back through the code, but I did find something in LDEV-3009 mentioning a new Application.cfc setting:

this.useJavaAsRegexEngine = true

This is documented for ColdFusion in Application variables, and… absolutely frickin' nowhere in the Lucee docs, as far as I can see.

On a whim I stuck that setting in my Application.cfc and re-ran the test. If the setting was false: the test doesn't work. If it was true: the test does work. That's something, but Lucee is not off the hook here. The behaviour of that regex match does not change between the old and new regex engines! .* always matches an empty string! So there's still a bug.

However, being pragmatic, I figured "problem solved" (for now), and moved on. For some reason I restarted my container, and re-hit my tests:

I switched the setting to this.useJavaAsRegexEngine = false and the tests ran again (failed incorrectly, but ran). So… let me get this straight. For TestBox to work, I need to set that setting to true. To get CFWheels to work, I need to set it to false.

For pete's sake.

As I said on the Lucee subchannel on the CFML Slack:

Do ppl recall how I've always said these stupid flags to change behaviour of the language were a fuckin dumb idea, and are basically unusable in a day and age where we all rely on third-party libs to do our jobs?

Exhibit. Fucking. A.

Every single one of these stupid, toxic, setting doubles the overhead for library providers to make their code work. I do not fault TestBox or CFWheels one bit here. They can't be expected to support the exponential number of variations each one of those settings accumulates. I can firmly say that no CFML code should ever be written that depends on any of these settings. And no library or third-party code should ever be tested with the setting variation on. Just ignore them. The settings should not exist. Anyway: this is an editorial digression. "We are where we are" as the over-used saying goes.


Screw all this. Seriously. All I wanted to do is to do a blog article about perhaps 50-odd new lines of code in my example app. Instead I spent four hours untangling this shite. And my blog article has not progressed.

Here's what I needed to do to my app to work around these various issues:

This is all committed to GitHub as 0.5.1

Writing this sure was cathartic. I think I was my own audience for this one. Ah well. Good on you if you got to here.

Righto.

--
Adam

Friday 15 April 2022

CFML: implementing dependency injection in a CFWheels web site

G'day:

Recently I wanted to abstract some logic out of one of our CFWheels model classes, into its own representation. Code had grown organically over time, with logic being inlined in functions, making a bunch of methods a bit weighty, and had drifted well away from the notion of following the "Single Responsibility Principle". Looking at the code in question, even if I separated it out into a bunch of private methods (it was a chunk of code, and refactoring into a single private method would not have worked), it was clear that this was just shifting the SRP violation out of the method, and into the class. This code did not belong in this class at all. Not least of all because we also needed to use some of it in another class. This is a pretty common refactoring exercise in OOP land.

I'm going to be a bit woolly in my descriptions of the functionality I'm talking about here, because the code in question is pretty business-domain-specific, and would not make a lot of sense to people outside our team. Let's just say it was around logging requirements. It wasn't, but that's a general notion that everyone gets. We need to log stuff in one of our classes, and currently it's all baked directly into that class. It's not. But let's pretend.

I could see what I needed to do: I'll just rip this lot out into another service class, and then use DI to… bugger. CFWheels does not have any notion of dependency injection. I mean... I'm fairly certain it doesn't even use the notion of constructors when creating objects. If one wants to use some code in a CFWheels model, one writes the code in the model. This is pretty much why we got to where we are. If one wants to use code from another class in one's model... one uses the model factory method to get a different model (eg: myOtherModel = model("Other")). Inline in one's code. There's a few drawbacks here:

  • In CFWheels parlance, "model" means "an ORM representation of a DB row". The hard-coupling between models and a DB table is innate to CFWheels. It didn't seem to occur to the architects of CFWheels that not all domain model objects map to underlying DB storage. One can have a "tableless model", but it still betrays the inappropriate coupling between domain model and storage representation. A logging service is a prime example of this. It is part of the domain model. It does not represent persisted objects. In complex applications, the storage considerations around the logic is just a tier underneath the domain model. It's not baked right into the model. I've just found a telling quote on the CFWheels website:
    Model: Just another name for the representation of data, usually a database table.
    Frameworks and CFWheels › Model-View-Controller (MVC)
    That's not correct. That is not what the model is. But explains a lot about CFWheels.
  • Secondly, it's still a bit of a fail of separation of concerns / IoC if the "calling code" hard-codes which implementation of a concern it is going to use.
  • If one is writing testable code, one doesn't want that second-point tight-coupling going on. If I'm testing features of my model, I want to stub out the logger it's using, for example. This is awkward to do if the decision as to which logger implementation we're using is baked-into the code I'm testing.

Anyway, you probably get it. Dependency injection exists for a reason. And this is something CFWheels appears to have overlooked.


0.1 - Baseline container

I have worked through the various bits and pieces I'm going to discuss already, to make sure it all works. But as I write this I am starting out with a bare-bones Lucee container, and a bare-bones MariaDB container (from my lucee_and_mariadb repo. I've also gone ahead and installed TestBox, and my baseline tests are passing:

Yes. I have a test that TestBox is installed and running correctly.

We have a green light, so that's a baseline to start with. I've tagged that in GitHub as 0.1.


0.2 - CFWheels operational

OK, I'll install CFWheels. But first my requirement here is "CFWheels is working". I will take this to mean that it displays its homepage after install, so I can test for that pretty easily:

it("displays the welcome page", () => {
    cfhttp(url="http://lucee:8888/index.cfm", result="httpResponse");

    expect(httpResponse.statusCode).toBe(200)
    expect(httpResponse.fileContent).toInclude("<title>CFWheels</title>")
})

I'm using TDD for even mundane stuff like this so I don't get ahead of myself, and miss bits I need to do to get things working.

This test fails as one would expect. Installing CFWheels is easy: just box install cfwheels. This installs everything in the public web root which is not great, but it's the way CFWheels works. I've written another series about how to get a CFWheels-driven web app working whilst also putting the code in a sensible place, summarised here: Short version: getting CFWheels working outside the context of a web-browsable directory, but life's too short to do all that horsing around today, so we'll just use the default install pattern. Note: I do not consider this approach to be appropriate for production, but it'll do for this demonstration.

After the CFWheels installation I do still have to fix-up a few things:

  • It steamrolled my existing Application.cfc, so I had to merge the CFWheels bit with my bit again.
  • Anything in CFWheels will only work properly if it's called in the context of a CFWheels app, so I need to tweak my test config slightly to accommodate that.
  • And that CFWheels "context" only works if it's called from index.cfm. So I need to rename my test/runTests.cfm to be index.cfm.

Having done that:

A passing test is all good, but I also made sure the thing did work. By actually looking at it:

I've tagged that lot again, as 0.2.

0.3 - A working model

I want to mess around with models, so I need to create one. I have a stub DB configured with this app, and it has a table test with a coupla test rows in it. I'll create a CFWheels model that maps to that. CFWheels expects plural table names, but mine's singular so I need a config tweak there. I will test that I can retrieve test records from it.

it("can find test records from the DB", () => {
    tests = model("Test").findAll(returnAs="object")

    expect(tests).notToBeEmpty()

    tests.each((test) => {
        expect(test).toBeInstanceOf("models.Test")
        expect(test.properties().keyArray().sort("text")).toBe(["id", "value"])
    })
})

And the implementation to make it pass:

import wheels.Model

component extends=Model {

    function config() {
        table(name="test")
    }
}

Good. Next I wanted to check when CFWheels calls my Test class's constructor. Given one needs to use that factory method (eg: model("Test").etc) to do anything relating to model objects / collections . etc, I was not sure whether the constructor comes into play. Why do i care? Because when using dependency injection, one generally passes the dependencies in as constructor arguments. This is not the only way of doing it, but it's the most obvious ("KISS" / "Principle of Least Astonishment") approach. So let's at least check.

it("has its constructor called when it is instantiated by CFWheels", () => {
    test = model("Test").new()

    expect(test.getFindMe()).toBe("FOUND")
})

Implementation:

public function init() {
    variables.findMe = "FOUND"
}

public string function getFindMe() {
    return variables.findMe
}

Result:

OK so scratch that idea. CFWheels does not call the model class's constructor. Initially I was annoyed about this as it seems bloody stupid. But then I recalled that when one is using a factory method to create objects, it's not unusual to not use the public constructor to do so. OK fair enough.

I asked around, and (sorry I forget who told me, or where they told me) found out that CFWheels does provide an event hook I can leverage for when an model object is created: model.afterInitialization. I already have my test set up to manage my expectations, so I can just change my implementation:

function config() {
    table(name="test")
    afterInitialization("setFindMe")
}

public function setFindMe() {
    variables.findMe = "FOUND"
}

And that passed this time. Oh I changed the test label from "has its constructor called…" to be "has its afterInitialization handler called…". But the rest of the test stays the same. This is an example of how with TDD we are testing the desired outcome rather than the implementation. It doesn't matter whether the value is set by a constructor or by an event handler: it's the end result of being able to use the value that matters.

At the moment I have found my "way in" to each object as they are created. I reckon from here I can have a DependencyInjectionService that I can call upon from the afterInitialization handler so the model can get the dependencies it needs. This is not exactly "dependency injection", it's more "dependency self-medication", but it should work.

I'll tag here before I move on. 0.3

0.4 integrating DI/1

My DI requirements ATM are fairly minimal, but I am not going to reinvent the wheel. I'm gonna use DI/1 to handle the dependencies. I've had a look at it before, and it's straight forward enough, and is solid.

My tests are pretty basic to start with: I just want to know it's installed properly and operations:

it("can be instantiated", () => {
    container = new framework.ioc("/services")

    expect(container).toBeInstanceOf("framework.ioc")
})

And now to install it: box install fw1

And we have a passing test (NB: I'm not showing you the failures necessarily, but I do always actually not proceed with anything until I see the test failing):

It's not much use unless it loads up some stuff, so I'll test that it can:

it("loads services with dependencies", () => {
    container = new framework.ioc("/services")

    testService = container.getBean("TestService")
    expect(testService.getDependency()).toBeInstanceOf("services.TestDependency")
})

I'm gonna show the failures this time. First up:

This is reasonable because the TestService class isn't there yet, so we'd expect DI/1 to complain. The good news is it's complaining in the way we'd want it to. TestService is simple:

component {

    public function init(required TestDependency testDependency) {
        variables.dependency = arguments.testDependency
    }

    public TestDependency function getDependency() {
        return variables.dependency
    }
}

Now the failure changes:

This is still a good sign: DI/1 is doing what it's supposed to. Well: trying to. And reporting back with exactly what's wrong. Let's put it (and, I imagine: you) out of its misery and give it the code it wants. TestDependency:

component {
}

And now DI/1 has wired everything together properly:

As well as creating a DI/1 instance and pointing it at a directory (well: actually I won't be doing that), I need to hand-crank some dependency creation as they are not just a matter of something DI/1 can autowire. So I'm gonna wrap-up all that in a service too, so the app can just use a DependencyInjectionService, and not need to know what its internal workings are.

To start with, I'll just make sure the wrapper can do the same thing we just did with the raw IoC object from the previous tests:

describe("Tests for DependencyInjectionService", () => {
    it("loads the DI/1 IoC container and its configuration", () => {
        diService = new DependencyInjectionService()

        testService = diService.getBean("DependencyInjectionService")

        expect(testService).toBeInstanceOf("services.DependencyInjectionService")
    })
})

Instead of testing the TestService here, I decided to use DependencyInjectionService to test it can… load itself

There's a bit more code this time for the implementation, but not much.

import framework.ioc

component {

    public function init() {
        variables.container = new ioc("")
        configureDependencies()
    }

    private function configureDependencies() {
        variables.container.declareBean("DependencyInjectionService", "services.DependencyInjectionService")
    }

    public function onMissingMethod(required string missingMethodName, required struct missingMethodArguments) {
        return variables.container[missingMethodName](argumentCollection=missingMethodArguments)
    }
}
  • It creates an IOC container object, but doesn't scan any directories for autowiring opportunities this time.
  • It hand-cranks the loading of the DependencyInjectionService object.
  • It also acts as a decorator for the underlying IOC instance, so calling code just calls getBean (for example) on a DependencyInjectionService instance, and this is passed straight on to the IOC object to do the work.

And we have a passing test:

Now we can call our DI service in our model, and the model can use it to configure its dependencies. First we need to configure the DependencyInjectionService with another bean:

it("loads TestDependency", () => {
    diService = new DependencyInjectionService()

    testDependency = diService.getBean("TestDependency")

    expect(testDependency).toBeInstanceOf("services.TestDependency")
})
private function configureDependencies() {
    variables.container.declareBean("DependencyInjectionService", "services.DependencyInjectionService")
    variables.container.declareBean("TestDependency", "services.TestDependency")
}
describe("Tests for TestDependency", () => {
    describe("Tests for getMessage method")
        it("returns SET_BY_DEPENDENCY", () => {
            testDependency = new TestDependency()

            expect(testDependency.getMessage()).toBe("SET_BY_DEPENDENCY")
        })
    })
})
// TestDependency.cfc
component {

    public string function getMessage() {
        return "SET_BY_DEPENDENCY"
    }
}

That's not quite the progression of the code there. I had to create TestDependency first, so I did its test and it first; then wired it into DependencyInjectionService.

Now we need to wire that into the model class. But first a test to show it's worked:

describe("Tests for Test model", () => {
    describe("Tests of getMessage method", () => {
        it("uses an injected dependency to provide a message", () => {
            test = model("Test").new()

            expect(test.getMessage()).toBe("SET_BY_DEPENDENCY")
        })
    })
})

Hopefully that speaks for itself: we're gonna get that getMessage method in Test to call the equivalent method from TestDependency. And to do that, we need to wire an instance of TestDependency into our instance of the Test model. I should have thought of better names for these classes, eh?

// /models/Test.cfc
import services.DependencyInjectionService
import wheels.Model

component extends=Model {

    function config() {
        table(name="test")
        afterInitialization("setFindMe,loadIocContainer")
    }

    public function setFindMe() {
        variables.findMe = "FOUND"
    }

    public string function getFindMe() {
        return variables.findMe
    }

    private function loadIocContainer() {
        variables.diService = new DependencyInjectionService()
        setDependencies()
    }

    private function setDependencies() {
        variables.dependency = variables.diService.getBean("TestDependency")
    }

    public function getMessage() {
        return variables.dependency.getMessage()
    }

}

That works…

…but it needs some adjustment.

Firstly I want the dependency injection stuff being done for all models, not just this one. So I'm going to shove some of that code up into the Model base class:

// /models/Model.cfc
/**
 * This is the parent model file that all your models should extend.
 * You can add functions to this file to make them available in all your models.
 * Do not delete this file.
 */

import services.DependencyInjectionService

 component extends=wheels.Model {

    function config() {
        afterInitialization("loadIocContainer")
    }

    private function loadIocContainer() {
        variables.diService = new DependencyInjectionService()
        setDependencies()
    }

    private function setDependencies() {
        // OVERRIDE IN SUBCLASS
    }
}
// models/Test.cfc
import wheels.Model

component extends=Model {

    function config() {
        super.config()
        table(name="test")
        afterInitialization("setFindMe")
    }
    
    // ...


    private function setDependencies() {
        variables.dependency = variables.diService.getBean("TestDependency")
    }
    
    // ...

}

Now the base model handles the loading of the DependencyInjectionService, and calls a setDependencies method. Its own method does nothing, but if a subclass has an override of it, then that will run instead.

I will quickly tag that lot before I continue. 0.4.

But…

0.5 Dealing with the hard-coded DependencyInjectionService initialisation

The second problem is way more significant. Model is creating and initialising that DependencyInjectionService object every time a model object is created. That's not great. All that stuff only needs to be done once for the life of the application. I need to do that bit onApplicationStart (or whatever approximation of that CFWheels supports), and then I need to somehow expose the resultant object in Model.cfc. A crap way of doing it would be to just stick it in application.dependencyInjectionService and have Model look for that. But that's a bit "global variable" for my liking. I wonder if CFWheels has an object cache that it intrinsically passes around the place, and exposes to its inner workings. I sound vague because I had pre-baked all the code up to where I am now a week or two ago, and it was not until I was writing this article I went "oh well that is shit, I can't be having that stuff in there". And I don't currently know the answer.

Let's take the red-green-refactor route, and at least get the initialisation out of Model, and into the application lifecycle.

Ugh. Looking through the CFWheels codebase is not for the faint-hearted. Unfortunately the "architecture" of CFWheels is such that it's about one million (give or take) individual functions, and no real sense of cohesion to anything other than a set of functions might be in the same .cfm (yes: .cfm file :-| ), which then gets arbitrarily included all over the place. If I dump out the variables scope of my Test model class, it has 291 functions. Sigh.

There's a bunch of functions possibly relating to caching, but there's no Cache class or CacheService or anything like that... there's just some functions that act upon a bunch of application-scoped variable that are not connected in any way other than having the word "cache" in them. I feel like I have fallen back through time to the days of CF4.5. Ah well.

I'll chance my arm creating my DependencyInjectionService object in my onApplicationStart handler, use the $addToCache function to maybe put it into a cache… and then pull it back out in Model. Please hold.

[about an hour passes. It was mostly swearing]

Okey doke, so first things first: obviously there's a new test:

describe("Tests for onApplicationStart", () => {
    it("puts an instance of DependencyInjectionService into cache", () => {
        diService = $getFromCache("diService")

        expect(diService).toBeInstanceOf("services.DependencyInjectionService")
    })
})

The implementation for this was annoying. I could not use the onApplicationStart handler in my own Application.cfc because CFWheels steamrolls it with its own one. Rather than using the CFML lifecycle event handlers the way they were intended, and also using inheritance when an application and an application framework might have their own work to do, CFWheels just makes you write its handler methods into your Application.cfc. This sounds ridiculous, but this is what CFWheels does in the application's own Application.cfc. I'm going to follow-up on this stupidity in a separate article, perhaps. But suffice it to say that instead of using my onApplicationStart method, I had to do it the CFWheels way. which is … wait for it… to put the code in events/onapplicationstart.cfm. Yuh. Another .cfm file. Oh well. Anyway, here it is:

<cfscript>
// Place code here that should be executed on the "onApplicationStart" event.
import services.DependencyInjectionService

setDependencyInjectionService()

private void function setDependencyInjectionService() {
    diService = new DependencyInjectionService()
    $addToCache("diService", diService)
}
</cfscript>

And then in models/Model.cfc I make this adjustment:

private function loadIocContainer() {
    variables.diService = new DependencyInjectionService()
    variables.diService = $getFromCache("diService")
    setDependencies()
}

And then…

I consider that a qualified sucessful exercise in "implementing dependency injection in a CFWheels web site". I mean I shouldn't have to hand-crank stuff like this. This article should not need to be written. This is something that any framework still in use in 2022 should do out of the box. But… well… here we are. It's all a wee bit Heath Robinson, but I don't think it's so awful that it's something one oughtn't do.

And now I'm gonna push the code up to github (0.5), press "send" on this, and go pretend none of it ever happened.

Righto.

--
Adam

Saturday 9 April 2022

CFML: revisit import aliasing

G'day:

I'm writing this here cos it's getting a bit long for a comment on the CFML Slack channel, and perhaps it might get a different set of eyes on it here anyhow.

I wanna revisit the discussion about import aliasing in CFML. ie this:

import com.vendor.app.package.Date as VendorDate
import org.project.lib.Date as LibDate

vendorDate = new VendorDate(now())
LibDate = new LibDate(now())

This has not been implemented in CFML because - I suspect - the various devs working on the CFML engines are primarily Java devs, and Java does not support this for (IMO) pretty bogus reasons (see the answer to Change Name of Import in Java, or import two classes with the same name, its commments and link within for the "reasoning").

However it's pretty common around the place in other languages, especially ones that occupy overlapping space as CFML, eg:

(It's important to consider that both Groovy and Kotlin are JVM languages with the remit of making Java easier, similar - in a way? - to CFML).

I've got by without this mostly, but just had a real world situation where the absence of it is a pain in the butt:

import logbox.system.logging.config.LogBoxConfig
import logbox.system.logging.LogBox
import services.logging.Config

// ...

config = new Config()
logboxConfig = new LogBoxConfig(config)

logbox = new LogBox(logboxConfig)

This is from a CFC, I've just elided the not relevant bits. Now whilst there are only 10 lines of code between where Config is imported and I use it, I still just went "what config is this? Oh right, my one". Bear in mind the CFC itself is nothing to do with logging, it's an IoC factory method. In context, "Config" suggests it's something to do with the IoC factory (this is the basis of my next article… the one I was working on when this situation presented itself). The code is just not as clear as it could be.

I know I can do this:

import services.logging.*

//...

config = new logging.Config()

But there's cross-over in the "logging" concept here with LogBox's logging any my own logging. Also it's a bit of shit work around because to me a * import indicates there's a bunch of stuff from that package being used, whereas there isn't; I'm just using that one class here. So def a work-around, not how one would naturally solve this.

Then I figured actually there was legit call to use import logbox.system.logging.* as I'm using multiple things from there, but that then conflicts with import services.logging.*

What would be good to do here would be:
import services.logging.Config as LoggingConfig

// ...

config = new LoggingConfig()

That is the clearest representation of what's going on, I think.

Anyway, just wondering what other CFMLers think. Maybe Java's right? Maybe all the other languages including the ones trying to improve on Java are... ;-)

Righto.

--
Adam

Tuesday 3 August 2021

CFML: static methods and properties

G'day:

Context

In Lucee 5.0 (so: ages ago) and ColdFusion 2021 (so: you know… just now), support for static properties and methods was added to CFML. This isn't a feature that you'd use very often, but it's essential to at least know about it. And it's bloody handy, and makes your code clearer sometimes. I had reason to be googling for docs / examples today, and what's out there ain't great so I thought I'd write my own "probably still not great" effort.

OK so that's the bar set suitably low. Let's get on with it.

What?

What are static properties and methods. I'm going to start from the other end of things. What are general properties and methods? They're properties and methods of an object; you know, one of those things one gets as the result of calling new MyComponent() (or from a createObject call if yer old skool). An object is a set of properties and methods that maintain a current state: values specific to that object.

son = new Person("Zachary")
dad = new Person("Adam")

We have two instances of the Person CFC: one for Zachary and one for me. The behaviour - ie: methods - of these Person objects is defined in Person.cfc, but when one calls an object method on a given object, it acts on the state of that specific object. So if one was to call son.getName(), one would get "Zachary"; if one was to call dad.getName(), one would get "Adam". The implementation of getName is the same for both son and dad - as defined by Person - but their implementation acts on the values associated with the object ("Zachary" and "Adam" respectively). You know all this stuff already, but it's just to contextualise things.

So what these object-based properties and methods are to an object; static properties and methods are to the class (the CFC itself). To get that, one has to have clear in one's mind that objects - instances of a class - are not the same thing as the class. Also it's important to understand that a CFC is not simply a file of source code: it still gets loaded into memory before any objects are made, and it is still a data structure in its own right, and it too can have its own properties, and methods to act on them.

We saw above examples of calling methods on an object. We can also call methods on the class itself, without needing an object. EG:

dog = Animal::createFromSpecies("canis familiaris")

Here we have an Animal class - and that is a reference to Animal.cfc, not an object created from Animal.cfc - and it has a factory method which we use to return a Dog object. Internally to createFromSpecies there might be some sort of look-up table that saying "if they ask for a 'canis familiaris', return a new Dog object"; the implementation detail doesn't matter (and I'll get to that), the important bit is that we are calling the method directly on the Animal class, not on an object. We an also reference static properties - properties that relate to the class - via the same :: syntax. I'll show a decent example of that shortly.

How?

Here's a completely contrived class that shows static syntax:

// Behaviour.cfc
component {

    static {
        static.defaultMyVarValue = "set in static constructor"
        static.myVar = static.defaultMyVarValue
    }

    static.myVar = "set in pseudo-constructor"

    public static function resetMyVar() {
        static.myVar = static.defaultMyVarValue
    }
}

There's a coupla relevant bits here.

The static constructor. This is to the class what the init method is to an object. When the class is first loaded, that static constructor is executed. It can only reference other static elements of the class. Obviously it can not access object properties or object methods, because when the static constructor is executed, it's executed on the class itself: there are no objects in play. the syntax of this looks a bit weird, and I don't know why this was picked instead of having:

public static function staticInit() {
    static.defaultMyVarValue = "set in static constructor"
    static.myVar = static.defaultMyVarValue
}

That's more clear I think, and doesn't require any new syntax. Oh well.

Note that when the class is first initialised the pseudo-constructor part of the CFC is not executed. That is only executed when a new object is created from the class.

A static method can only act on other static elements of the class, same as the static constructor, and for the same reason.

I demonstrate the behaviour of this code in its tests:

import testbox.system.BaseSpec
import cfmlInDocker.miscellaneous.static.Behaviour

component extends=BaseSpec {

    function run() {

        describe("Tests for Behaviour class", () => {
            beforeEach(() => {
                Behaviour::resetMyVar()
            })

            it("resets the test variable when resetMyVar is called", () => {
                behaviour = new Behaviour()
                expect(behaviour.myVar).toBe("set in pseudo-constructor")
                expect(Behaviour::myVar).toBe("set in pseudo-constructor")
                Behaviour::resetMyVar()
                expect(behaviour.myVar).toBe("set in static constructor")
                expect(Behaviour::myVar).toBe("set in static constructor")
            })

            it("doesn't use the pseudo-constructor for static values using class reference", () => {
                expect(Behaviour::myVar).toBe("set in static constructor")
            })

            it("does use the pseudo-constructor for static values using object reference", () => {
                behaviour = new Behaviour()
                expect(behaviour.myVar).toBe("set in pseudo-constructor")
            })
        })
    }
}

I need that resetMyVar method there just to make the testing easier. One thing to consider with static properties is that they belong to the class, and that class persists for the life of the JVM, so I want to make the initial state of the class for my tests the same each time. It's important to fully understand that when one sets a static property on a class, that property value will be there for all usages of that class property for the life of the JVM. So it persists across requests, sessions and even the lifetime of the application itself.

Why?

Properties

That Behaviour.cfc example was rubbish. It gives one no sense of why on might want to use static properties or methods. Here's a real world usage of static properties. I have a very cut down implementation of a Response class; like one might have in an MVC framework to return the value from a controller that represents what needs to be sent back to the client agent.

// Response.cfc
component {

    static {
        static.HTTP_OK = 200
        static.HTTP_NOT_FOUND = 404
    }

    public function init(required string content, numeric status=Response::HTTP_OK) {
        this.content = arguments.content
        this.status = arguments.status
    }

    public static function createFromStruct(required struct values) {
        return new Response(values.content, values.status)
    }
}

Here we are using static properties to expose some labelled values that calling code can use to create their response objects. One of its tests demonstrates this:

it("creates a 404 response", () => {
    testContent = "/bogus/path was not found"
    response = new Response(testContent, Response::HTTP_NOT_FOUND)

    expect(response.status).toBe(Response::HTTP_NOT_FOUND)
    expect(response.content).toBe(testContent)
})

Why not just use the literal 404 here? Well for common HTTP status codes like 200 and 404, I think they have ubiquity we could probably get away with. But what about a FORBIDDEN response. What status code is that? 403? Or is it 401? I can never remember, can you? So what wouldn't this be more clear, in the code:

return new Response("nuh-uh, sunshine", Response::HTTP_FORBIDDEN)

I think that's fair enough. But OK, why are they static? Why not just use this.HTTP_FORBIDDEN? Simply to show intended usage. Do HTTP status codes vary from object to object? Would one Response object have a HTTP_BAD_GATEWAY of 502, but another one having a HTTP_BAD_GATEWAY value of 702? No. These properties are not part of an object's state, which is what's implied by using the this scope (or variables scope). They are specific to the Response class; to the concept of what it is to be a Response.

Methods

That Response.cfc has a static method createFromStruct which I was going to use as an example here:

it("returns a Response object with expected values", () => {
    testValues = {
        content = "couldn't find that",
        status = Response::HTTP_NOT_FOUND
    }
    response = Response::createFromStruct(testValues)

    expect(response.status).toBe(testValues.status)
    expect(response.content).toBe(testValues.content)
})

But it occurred to me after I wrote it that in CFML one would not use this strategy: one would simply use response = new Response(argumentCollection=testValues). So I have a request class instead:

// Request.cfc
component {

    public function init(url, form) {
        this.url = arguments.url
        this.form = arguments.form
    }

    public static Request function createFromScopes() {
        return new Request(url, form)
    }
}

This is the other end of the analogy I started with an MVC controller. This is a trimmed down implementation of a Request object that one would pass to one's controller method; encapsulating all the elements of the request that was made. Here I'm only using URL and form values, but a real implementation would also include cookies, headers, CGI values etc too. All controller methods deal in the currency of "requests", so makes sense to pass a Request object into them.

Here we have a basic constructor that takes each property individually. As a rule of thumb, my default constructor always just takes individual values for each property an object might have. And the constructor implementation just assigns those values to the properties, and that's it. Any "special" way of constructing an object (like the previous example where's there's not discrete values, but one struct containing everything), I use a separate method. In this case we have a separate "factory" method that instead of taking values, just "knows" that most of the time our request will comprise the actual URL scope, and the actual form scope. So it takes those accordingly.

That's all fairly obvious, but why is it static? Well, if it wasn't static, we'd need to start with already having an object. And to create the object, we need to give the constructor some values, and that… would defeat the purpose of having the factory method, plus would also push implementation detail of the Request back into the calling code, which is not where that code belongs. We could adjust the constructor to have all the parameters optional, and then chain a call to an object method, eg:

request = new Request().populateFromScopes() 

But I don't think that's semantically as good as having the factory method. But it's still fine, that said.

My rationale for using static methods that way is that sometimes creating the object is harder than just calling its constructor, so wrap it all up in a factory method. Now static methods aren't only for factories like that. Sometimes one might need to implement some behaviour on static properties, and to do that, it generally makes sense to use a static method. One could use an object method, but then one needs to ask "is it the job of this object to be acting on properties belonging to its class?" The answer could very well be "no". So implement the code in the most appropriate way: a static method. But, again, there could be legit situations where it is the job of an object method to act upon static properties. Code in object methods can refer to static properties as much as they need to. Just consider who should be doing the work, and design one's implementation accordingly.

A note on syntax

I could not think of a better place to put this, further up.

I've shown how to call static methods and access static properties via the class, it's: TheClassName::theStaticMethodName() (or TheClassName::theStaticPropertyName). However sometimes you have an object though, and legit need to access static elements of it. In this situation use the dot operator like you usually would when accessing an object's behaviour: someObject.theStaticMethodName() (or someObject.theStaticPropertyName). As per above though, always give thought to whether you ought to be calling the method/property via the object or via the class though. It doesn't matter, but it'll make your code and your intent clearer if you use the most appropriate syntax in the given situation.

Outro

Bottom line it's pretty simple. Just as an object can have state and behaviour; so can the class the object came from. Simple as that. That's all static properties and methods are.

Righto.

--
Adam

 

Oh. There are official docs of sorts:

Monday 12 July 2021

Factories, static methods, Law of Demeter and never letting Sean see my code

G'day:

I'm absolutely kidding about one of those things, btw.

Right so yesterday I wrote a coupla articles about what code should and should not go into a controller:

Within that first one, I provided some sample code to demonstrate my point. Part of it was the usage of a factory class to provide some necessary wiring for my model class, which I then had as a dependency of my controller:

// UserContentFactory.cfc
component {

    function init(
        ValidationService validationService,
        UserFactory userFactory,
        UserContentService contentService,
        TwitterService twitterService,
        FacebookService facebookService
    ) {
        variables.validationService = arguments.validationService
        variables.userFactory = arguments.userFactory
        variables.contentService = arguments.contentService
        variables.twitterService = arguments.twitterService
        variables.facebookService = arguments.facebookService
    }

    function getUserContent() {
        userContent = new UserContent()
        userContent.setValidationService(validationService)
        userContent.setUserFactory(userFactory)
        userContent.setContentService(contentService)
        userContent.setTwitterService(twitterService)
        userContent.setFacebookService(facebookService)
        
        return userContent
    }
}
// UserContent.cfc
component accessors=true invokeImplicitAccessor=true {

    property publishedContent;
    property twitterContent;
    property facebookContent;

    function init(publishedContent, twitterContent, facebookContent) {
        variables.publishedContent = arguments.publishedContent
        variables.twitterContent = arguments.twitterContent
        variables.facebookContent = arguments.facebookContent
    }
    
    function setValidationService(ValidationService validationService) {
        variables.validationService = arguments.validationService
    }
    
    function setUserFactory(UserFactory userFactory) {
        variables.userFactory = arguments.userFactory
    }
    
    function setContentService(UserContentService contentService) {
        variables.contentService = arguments.contentService
    }
    
    function setTwitterService(TwitterService twitterService) {
        variables.twitterService = arguments.twitterService
    }
    
    function setFacebookService(FacebookService facebookService) {
        variables.facebookService = arguments.facebookService
    }
    
    function loadContentByFilters(required struct filters) {
        validFilters = validationService.validate(filters, getValidationRules()) // @throws ValidationException
        
        user = userFactory.getById(validFilters.id) // @throws UserNotFoundException
        
        publishedContent = contentService.getUserContent(validFilters)
        twitterContent = twitterService.getUserContent(validFilters)
        facebookContent = facebookService.getUserContent(validFilters)
    }
    
    private function getValidationRules() { 
    	// ... elided to save space
    }
}
// UserContentController.cfc
component {

    function init(ViewService viewService, UserContentFactory userContentFactory) {
        variables.viewService = arguments.viewService
        variables.userContentFactory = arguments.userContentFactory
    }

    function getContent(rawArgs) {
        try {
            userContent = userContentFactory.getUserContent().loadContentByFilters(rawArgs)
            
            renderedResponse = viewService.renderView("userContentView", userContent)
            
            return new HtmlResponse(renderedResponse)
            
        } catch (ValidationException, e) {
            return new ClientErrorResponse(400, e)
        } catch (UserNotFoundException e) {
            return new ClientErrorResponse(404, e)
        }
    }
}

(Note that all of this is untested basically "pseudo"-code. I've never run it, it's just a theoretic approach to the issue. By all means let me know if there's anything syntactically or logically wrong with it, but it's not code intended to be run)

loadContentByFilters relied on five external services to do its stuff, and I didn't want to pass those services to UserContent as constructor arguments because then they'd get mungled up with the data values I wanted the UserContent model's constructor to focus on, so I was using setter injection to configure UserContent with those, and have that factory class and method to deal with all that in the background (via whatever dependency injection / IOC container framework I would be using). By the time the controller received the factory it'd been configured, and it had been able to configure a UserContent object, so all the controller needed to do was to call it.

All simple enough.

Sean popped a comment against the article, I responded, and he came back to me again. At which point I decided I'd write this article instead of replying again:

Sean:

Since you're being so hardcore about drawing lines between the MVC parts of your code, I'm going to call this into question:

userContentFactory.getUserContent().loadContentByFilters(rawArgs)

There's an argument often made that you shouldn't have method chaining like this since it couples the caller to the structure of the chained method calls on another object. The argument goes that you should instead have something like:

userContentFactory.getUserContentByFilters(rawArgs)

Thoughts?


Adam:

Yeah. I actually started with that! But then I didn't think it was the business of the factory to go as far as the actual data loading. But… I was not entirely convinced either way, to be completely honest.

I'm also completely down with the Law of Demeter when it comes to chaining methods like that. I did think about that at the time too.

The conclusion I drew was that the factory was just a "convenience extraction", or a utility or something that is completely "in the service" of UserContent. It serves absolutely no purpose other than a mechanism (I misspelled that as "MEHchanism" at first, perhaps fittingly) to wire-together the dependencies that loadContentByFilters needs to do its job. I did not want the controller to have to know how to do that.

The second draft of the actual code had two separate statements:

userContent = userContentFactory.getUserContent()
userContent.loadContentByFilters(rawArgs)

Which is functionally the same, and has the same Law of Demeter considerations you raised, but maybe the thinking is clearer: useContent = userContentFactory.getUserContent() is just an analogy of useContent = new UserContent(). I just slapped it into one statement because that was more convenient (ahem) for the point I was trying to make.

A simpler scenario would be if I wanted to be able to create a UserContent object via a struct that contained name/value pairs matching the constructor params it needs, I'd simply have a (probably static) method on the UserContent class which was createFromStruct which would internally grab the bits and pieces out of the struct and return a new UserContent object, passing those values to its constructor. No need for any separate factory cos there's no external dependencies to have to deal with somehow.

I knew that bit of the code would cause consternation from some quarters... and was hoping one of the quarters would be your own ;-).


Sean:

I find it interesting that you might subsume a factory for createFromStruct() as a static method but not for loadByFilters()…?

Now that is a very good point.

Truth be told, I simply didn't think of it at the time I was writing the blog article, but I did recall having done this in the past by the time I was writing the reply to Sean's question.

But let me back up a bit. Law of Demeter. In summary it means it's OK to call methods on a dependency, but it gets decreasingly OK to call method on objects that the methods of the dependency return, and so on. This is poor form:

function myMethod() {
    return someDependency.someMethod().someMethodOfAnotherObject().wowWeAreNowALongWayAwayFromTheDependency()
}

This is because not only is your implementation tightly coupled to its dependency - but we kinda need to live with that - it's also tightly coupled to the objects that implement someMethodOfAnotherObject and wowWeAreNowALongWayAwayFromTheDependency. You should not need to do all that. SomeDependency should take care of it for you. It should not be exposing its inner workings like that.

BTW, this does not apply to objects that implement a fluent interface. This is fine:

function myMethod() {
    return obj.someMethod().someOtherMethodOfObj().andAnotherMethodOfObj()
}

All those methods are part of the API exposed by obj.

So that's what Sean pulled me up on:

userContent = userContentFactory.getUserContent().loadContentByFilters(rawArgs)

That's a bit of a LoD violation, and worth pointing out. I did reason through this (before Sean raised it, as I said), and I'm fine with it.

But Sean's nudging of me back towards static methods solves this.

We could implement UserContent like this:

// UserContent.cfc
component accessors=true invokeImplicitAccessor=true {

    property publishedContent;
    property twitterContent;
    property facebookContent;

    function init(publishedContent, twitterContent, facebookContent) {
        variables.publishedContent = arguments.publishedContent
        variables.twitterContent = arguments.twitterContent
        variables.facebookContent = arguments.facebookContent
    }
    
    static function setValidationService(ValidationService validationService) {
         static.validationService = arguments.validationService
     }
     // etc, all the methods to set the services are static methods, setting a static property
    
    static function loadContentByFilters(required struct filters) {
        validFilters = validationService.validate(filters, getValidationRules()) // @throws ValidationException
        
        user = userFactory.getById(validFilters.id) // @throws UserNotFoundException
        
        return new UserContent(
            contentService.getUserContent(validFilters)
            twitterService.getUserContent(validFilters)
            facebookService.getUserContent(validFilters)
        )
    }
    
    // irrelevant stuff snipped
}
  • One sets the dependency services via static methods;
  • loadContentByFilters is also a static method which does its stuff, and returns a new instance of itself with the data values set.
  • Not shown here is that when the IoC framework creates the dependency container, it runs the factory method then, to set the services into the UserContent class (NB: "class", not "object") once.

Now in the controller we have a coupla options:

userContent = UserContent::loadContentByFilters(rawArgs)

No need to pass the class in as a constructor argument or anything (just as well: one cannot pass classes around as arguments), on just calls the method on the class.

Inlining static method calls like this is not so good for testing, that said, as it kinda bypasses dependency injection just like having new UserContent() in there, so this is less than ideal.

However there's nothing to stop us passing in an uninintialised instance of UserContent into the controller as a dependency. The IoC handling of this would be along the lines of:

container.set("UserContent", (container) => createObject("UserContent")) //NB: *not* using `new`, cos that calls the constructor
container.set("UserContentController", (container) => new UserContentController(container.get("ViewService"), container.get("UserContent")))

Then we use the dot operator instead of the :: operator to call the static loadContentByFilters via the object reference to the class: userContent

userContent = userContent.loadContentByFilters(rawArgs)

Better yet, perhaps, would be to just make the constructor arguments optional, and just use new UserContent() ;-)

Note: only ColdFusion 2021 has static support, and only Lucee 5.3.8 and above allows using the dot operator to call a static method on an object.


No doubt Sean will find other points of order from all this, but that's great cos it gives me (/us) an opportunity to think about stuff some more.

Right now I'm thinking about another beer, so I'm gonna do that.

Tomorrow I need to have a prod of CFML's handling of static stuff. I've already spotted something that looks like a bug in ColdFusion (quelle frickin surprise), but I need to check some stuff first, and I CBA doing it now. Because beer.

Righto.

--
Adam

Thursday 1 July 2021

One last one! CFML higher-order functions compared to tag-based code: reduceRight

G'day:

I forgot one!

I've already discussed map, reduce, filter, sort, some, every and each, operations; but recently reduceRight was added to CFML (well: at least in ColdFusion it was; it's not in Lucee yet) as well.

I have to start my day job in 16min, so this will be quick.

reduceRight is the same as reduce, except it starts from the end of the collection, not the beginning:

colours = ["Whero","Karaka","Kowhai","Kakariki","Kikorangi","Poropango","Papura"]

coloursAsList = colours.reduce((all="", colour) => all.listAppend(colour))
coloursAsReversedList = colours.reduceRight((all="", colour) => all.listAppend(colour))

writeOutput("coloursAsList: #coloursAsList#<br>coloursAsReversedList: #coloursAsReversedList#<br>")
coloursAsList: Whero,Karaka,Kowhai,Kakariki,Kikorangi,Poropango,Papura
coloursAsReversedList: Papura,Poropango,Kikorangi,Kakariki,Kowhai,Karaka,Whero

Yes yes Mingo; one would not use reduce to convert an array of strings to a list. That is beside the point. But thanks for letting me know Lucee (but not ColdFusion) has an Array.reverse method, which would be a better way to reverse the list order here: colours.reverse().toList().

And the tags version, just a reversed counting loop does the trick here:

<cfset coloursAsReversedList = "">
<cfloop index="i" from="#arrayLen(colours)#" to="1" step="-1">
    <cfset coloursAsReversedList = listAppend(coloursAsReversedList, colours[i])>
</cfloop>

That's it. four minutes to get to work. Fortunately that's just a matter of switching desktops…

Righto.

--
Adam

CFML higher-order functions compared to tag-based code: some, every and each functions

G'day:

I'm gonna try to round out this short series today: there's not much to say about the some, every and each methods in the context of comparing their functionality to old-school tag-based code. As a reminder, I've already covered map, reduce, filter and sort operations.

some

some iterates over the collection, calling a callback on each element, and will exit as soon as the callback returns true for an element. An example might be checking if at least some class members passed (or failed) their test:

examResults = [
    {person="Alex", mark=75},
    {person="Billie", mark=52},
    {person="Charlie", mark=41},
    {person="Daryl", mark=29},
    {person="Evan", mark=53}
]

somePassed = examResults.some((result) => result.mark >= 50)

writeOutput("Some of the class passed the test? #somePassed#<br><hr>")


someFailed = examResults.some((result) => {
    writeOutput("Called for #result.person#, #result.mark#<br>")
    return result.mark < 50
})

In the second example there I show a difference between this iteration function and the others we've encountered so far. All the others always iterate through the entire collection, however some and every do not. They exit as soon as they can answer the question. So as soon as some gets a true it exits; as soon as every gets a false it exits. The output of this is:

Some of the class passed the test? true

Called for Alex, 75
Called for Billie, 52
Called for Charlie, 41

In this case it only got as far as the first false result from the callback (because, sadly, Charlie did not make the cut)

The tag-based version of this would be:

<cfset somePassed = false>
<cfloop array="#examResults#" item="result">
    <cfif result.mark GTE 50>
        <cfset somePassed = true>
        <cfbreak>
    </cfif>
</cfloop>
<cfoutput>Some of the class passed the test? #somePassed#<br><hr></cfoutput>

<cfset someFailed = false>
<cfloop array="#examResults#" item="result">
    <cfoutput>Called for #result.person#, #result.mark#<br></cfoutput>
    <cfif result.mark LT 50>
        <cfset someFailed = true>
        <cfbreak>
    </cfif>
</cfloop>

Again with the boilerplate code (ref from previous articles).

BTW, don't get carried away with these higher-order functions if there's another built-in function to do the job. Recently I checked if something was in an array by doing this:

colours = ["Whero","Karaka","Kowhai","Kakariki","Kikorangi","Poropango","Papura"]

containsGreen = colours.some((colour) => colour == "Kakariki")
writeOutput("It contains green: #containsGreen#<br>")

My boss gently pointed out I could just do this:

containsGreen = !!colours.find("Kakariki")

Use the simpler option where possible ;-)

every

every is the opposite of some: it exits as soon as the callback returns false. Our example here would be to check if everyone passed the exam:

everyonePassed = examResults.every((result) => {
    writeOutput("Called for #result.person#, #result.mark#<br>")
    return result.mark >= 50
})
writeOutput("Everyone passed the test? #everyonePassed#<br><hr>")
Called for Alex, 75
Called for Billie, 52
Called for Charlie, 41
Everyone passed the test? false

The tag-based equivalent is the usual "mostly boilerplate" thing:

<cfset everyonePassed = true>
<cfloop array="#examResults#" item="result">
    <cfoutput>Called for #result.person#, #result.mark#<br></cfoutput>
    <cfset personPassed =  result.mark GTE 50>
    <cfif NOT personPassed>
        <cfset everyonePassed = false>
        <cfbreak>
    </cfif>
</cfloop>
<cfoutput>Everyone passed the test? #everyonePassed#<br><hr></cfoutput>

each

Sometimes it's not a data transformation that one needs when iterating over a collection. If none of the other options do the trick, there's the generic each method:

examResults.each((result) => {
    writeOutput("Name: #result.person#, mark: #result.mark#<br>")
})

As a general rule never start solving an iteration task with each. Consider if one of the other more situation-specific methods are a better fit. It's seldom that each is the right answer.

And the tag equivalent is pretty much the same, because - really - all the tag version does is "each"; it's down to the inner code block to distinguish between the various iteration possibilities:

<cfloop array="#examResults#" item="result">
    <cfoutput>Name: #result.person#, mark: #result.mark#<br></cfoutput>
</cfloop>

OK that's it. Tag-based CFML versions of the more situation-descriptive and less boilerplate iteration higher-order functions. If you need anything else about them explained, let me know.

Righto.

--
Adam

Wednesday 30 June 2021

CFML higher-order functions compared to tag-based code: sort function

G'day:

OK so you've probably got the gist of things with these articles, with my previous treatments of comparing "modern" to "old school" with map, reduce, filter operations. On to sorting now.

I think this is going to involve some awful code.

I don't think I need to explain why we might need to sort a collection, or what "sorting" is. It's really easy using higher-order functions. The need to write the sorting algorithm has been removed, and only a function to compare to elements needs to be provided:

months = [
    {id=1, miSequence=8, mi="Kohi-tātea", anglicised="Hānuere", en="January"}, 
    {id=2, miSequence=9, mi="Hui-tanguru", anglicised="Pēpuere", en="February"}, 
    {id=3, miSequence=10, mi="Poutū-te-rangi", anglicised="Maehe", en="March"}, 
    {id=4, miSequence=11, mi="Paenga-whāwhā", anglicised="Āperira", en="April"}, 
    {id=5, miSequence=12, mi="Haratua", anglicised="Mei", en="May"}, 
    {id=6, miSequence=1, mi="Pipiri", anglicised="Hune", en="June"}, 
    {id=7, miSequence=2, mi="Hōngongoi", anglicised="Hūrae", en="July"}, 
    {id=8, miSequence=3, mi="Here-turi-kōkā", anglicised="Akuhata", en="August"}, 
    {id=9, miSequence=4, mi="Mahuru", anglicised="Hepetema", en="September"}, 
    {id=10, miSequence=5, mi="Whiringa-ā-nuku", anglicised="Oketopa", en="October"}, 
    {id=11, miSequence=6, mi="Whiringa-ā-rangi", anglicised="Noema", en="November"}, 
    {id=12, miSequence=7, mi="Hakihea", anglicised="Tihema", en="December"}
]
monthsInMaoriOrder = duplicate(months).sort((e1, e2) => e1.miSequence - e2.miSequence)

writeDump(monthsInMaoriOrder)

Here I have a list of the months of the year, ordered according to the Gregorian calendar. The Maori calendar has the same ordering, but the year starts around when the Gregorian calendar considers June. So the exercise here is to re-order the array to respect that ordering. The code for the sorting is just the comparator function.

One thing to note here is that despite appearances given we're assigning the return value of the sorting operation to a new variable, the original array is modified when you call sort on it. I think this is less than ideal, but it's the way it works on both ColdFusion and Lucee. If you want you're original array left alone, then duplicate it first like I have here.

If we're going old school procedural: it's a bit of a nightmare. We need to write our own sorting implementation. Well: we grab one from cflib.org anyhow. But even then, the original leverages a callback function, so I've modified this to be truly procedural and have that embedded in the implementation.

<cffunction name="monthsSortedByMaoriSequence" returntype="array" output="false">
    <cfargument name="arrayToCompare" type="array" required="true">

    <cfset var lesserArray = arrayNew(1)>
    <cfset var greaterArray = arrayNew(1)>
    <cfset var pivotArray = arrayNew(1)>
    <cfset var examine = 2>
    <cfset var comparison = 0>
    <cfset pivotArray[1] = arrayToCompare[1]>

    <cfif  arrayLen(arrayToCompare) LT 2>
        <cfreturn arrayToCompare>
    </cfif>

    <cfset arrayDeleteAt(arrayToCompare, 1)>
    <cfloop array="#arrayToCompare#" item="element">
        <cfset comparison = element.miSequence - pivotArray[1].miSequence>

        <cfswitch expression="#sgn(comparison)#">
            <cfcase value="-1">
                <cfset arrayAppend(lesserArray, element)>
            </cfcase>
            <cfcase value="0">
                <cfset arrayAppend(pivotArray, element)>
            </cfcase>
            <cfcase value="1">
                <cfset arrayAppend(greaterArray, element)>
            </cfcase>
        </cfswitch>
    </cfloop>

    <cfif arrayLen(lesserArray)>
        <cfset lesserArray = monthsSortedByMaoriSequence(lesserArray)>
    <cfelse>
        <cfset lesserArray = arrayNew(1)>
    </cfif>

    <cfif arrayLen(greaterArray)>
        <cfset greaterArray = monthsSortedByMaoriSequence(greaterArray)>
    <cfelse>
        <cfset greaterArray = arrayNew(1)>
    </cfif>

    <cfset arrayAppend(lesserArray, pivotArray, true)>
    <cfset arrayAppend(lesserArray, greaterArray, true)>

    <cfreturn lesserArray>
</cffunction>
<cfset sorted = monthsSortedByMaoriSequence(months)>

It's hard to see the bit that the modern implementation needs, but it's buried here.

Note: to an clever clogs who spot the odd shortcoming in that implementation of quicksort: you're missing the point of the article, and also yer talking to the wrong person because I didn't write it. But - yes yes - you're very clever.

The point is: that's awful. Writing old-school tag-based procedural code one needs to re-implement (and re-test!) the sorting function every time you need one. This is an extreme example and only a lunatic would not use the callback approach even with tag based code:

<cffunction name="comparator">
    <cfargument name="e1">
    <cfargument name="e2">
    <cfreturn e1.miSequence - e2.miSequence>
</cffunction>

<cfset sorted = duplicate(months)>
<cfset arraySort(sorted, comparator)>

But still: it's just better to get with the programme (or the decade) and use the modern version for this.

Righto.

--
Adam