Sunday, 23 February 2014

ColdFusion 11: member functions implementations and suggestions

G'day:
Enough of the rhetoric, here's some code. I'm having a look at the new member functions in ColdFusion 11 today. And have some samples, observations and suggestions.

Here's a quick (and terribly contrived) example of using string / list member functions in ColdFusion 11:


s =    "The";
s =    s.append("quick brown fox", " ")
    .append("jumps over the lazy dog", " ")
    .ucase()
    .reverse();
writeOutput(s); 

Output:
GOD YZAL EHT REVO SPMUJ XOF NWORB KCIUQ EHT

Here I'm showing the member-function-version of listAppend(), ucase() and reverse(). I also demonstrate that - because string functions return the resultant string - how member functions can be chained together. This example is a dumb usage of that, but you can possibly see the potential there, and it's great that it's good object-oriented code instead of the old-school procedural stuff we've had to use in ColdFusion until now.

One thing one cannot do with the current implementation is to apply member functions to literal values, eg this JavaScript:

"my string".toUpperCase()

But we're still in beta, so there's time for that yet: 3712122.

We can also use member functions with the rest of the CFML datatypes. Here's some stuff using query member functions:

rainbow = queryNew("");
rainbow.addColumn("id", "integer", [1,2,3,4,5,6,7]);
rainbow.addColumn("en", "varchar", ["red","orange","yellow","green","blue","indigo","violet"]);
rainbow.addColumn("mi", "varchar", ["whero","karaka","kowhai","kakariki","kikorangi","tawatawa","mawhero"]);
writeDump(rainbow);

Output:
query
ENIDMI
1red1whero
2orange2karaka
3yellow3kowhai
4green4kakariki
5blue5kikorangi
6indigo6tawatawa
7violet7mawhero

This is cool, but I'd like to make two observations. We still have to use a bit of procedural code here to create the query in the first place, and to generate its dump. I'm less concerned about the latter, but I think we need some tweaking of the former. Also, due to the very literal way the Adobe ColdFusion bods have implemented the member functions, we can't chain these query functions together as they don't return the query, they return a boolean (which was always a stupid thing for the procedural functions to return, too).

Here's some CFML-written proof of concept code which could improve things a bit. The CFC is simply a series of stub functions which demonstrate the techniques, but I am not suggesting it is part of a solution. I just can't write Java well enough to do a proper job of it. Here's the CFC:

//Q.cfc
component{

    Q function new(required string cols, string types="", any data=[]){
        this.records = queryNew(cols, types, data);
        return this;
    }

    Q function addColumn(required string col, required string type, required array data){
        this.records.addColumn(col, type, data);
        return this;
    }

    Q function addRow(numeric rows=1, array data){
        if (structKeyExists(arguments, "data")){
            this.records.addRow(col);
        }else{
            this.records.addRow(col, data);
        }
        return this;
    }

    Q function setCell(required string col, required any value, numeric row){
        if (structKeyExists(arguments, "row")){
            this.records.setCell(col, value, row);
        }else{
            this.records.setCell(col, value);
        }
        return this;
    }

    Q function execute(required string sql, any params=[], struct options){
        if (structKeyExists(arguments, "options")){
            this.records = queryExecute(sql, params, options);
        }else{
            this.records = queryExecute(sql, params);
        }
        return this;
    }

    string function dump(){
        var result="";
        savecontent variable="result" {
            writeDump(var=this.records, attributeCollection=arguments);
        }
        return result;
    }

    string function toJson(){
        return serializeJson(this.records);
    }

}

And, more importantly, the code using it:

query = new Q()
        .new("")
        .addColumn("id", "integer", [1,2,3,4,5,6,7])
        .addColumn("en", "varchar", ["red","orange","yellow","green","blue","indigo","violet"])
        .addColumn("mi", "varchar", ["whero","karaka","kowhai","kakariki","kikorangi","tawatawa","mawhero"])
        .execute("SELECT * FROM this.records WHERE id = ?", [1], {dbtype="query"})
;
writeOutput(query.dump());
writeOutput(query.toJson());

Output:

query
RESULTSET
query
ENIDMI
1red1whero
CACHEDfalse
EXECUTIONTIME3
SQLSELECT * FROM this.records WHERE id = ?
SQLPARAMETERS
array
11
{"COLUMNS":["ID","EN","MI"],"DATA":[[1,"red","whero"]]}

Now... I'll repeat this: the CFC is just stub code, and intended solely to facilitate the second bit of code actually working. I am not suggesting we should need to go query = new Q(), or access the data via query.records, nor should the solution be a CFC. That is just to hopefully short-circuit some "missing the point entirely" comments I might end up with.

The key points are:

  • creating a new query should be object-oriented too. We should not rely on an old procedural function to facilitate OO code. How should we do this? I think the query class should have a static method new(), which we'd call thus:

    query = Query.new();

    where Query is the class, not the object (3712126).
  • Likewise the Query class should have a static method execute(), which acts the way queryExecute() currently does. This would either hit the database for data, or perhaps act upon itself (FROM [self], or something, in the SQL?) (3712127).
  • The member functions that manipulate the query data should return the query data, so subsequent methods can be chained to it (3712125).
  • There are other functions which currently act on a query object, like serializeJson(), toString(), writeDump(), WDDXifying, etc, which should also be member functions of the Query class (3712128).
I think the current implementation of member functions is a very literal transliteration from the procedural functions, and really lack a bit of nous or ambition in their reach. But, again, we're only in beta with ColdFusion 11 as yet, so there's scope to get this more fleshed out. I think perhaps during this first round the Adobe guys have been too literal with the "make the procedural functions be member functions". Whereas a better approach would be "implement a ColdFusion Query class", worrying less about the old procedural code, but really just focus on what a Query class should have for an API.

NB: I did not check the struct or array (etc) member functions, but assume they - likewise - can't be chained because they don't return useful values. Obviously this should be dealt with too.

I'll obviously post more observations as they come to mind.

--
Adam