Does ColdFusion support delegates?
-
26-10-2019 - |
Question
I have several database access methods that are wrapped in a try/catch block:
function GetAll() {
try {
entityLoad("Book");
}
catch (any e) {
throw (type="CustomException", message="Error accessing database, could not read");
}
}
function Save(Book book) {
try {
entitySave(book);
}
catch (any e) {
throw (type="CustomException", message="Error accessing database, could not save);
}
}
As you can see, the try/catch block is repeated several times, where the only thing that varies is the message. Is it possible to create a delegate in ColdFusion so that I can do something like this instead (using a C# lambda to represent an anonymous delegate)?:
function GetAll() {
DatabaseOperation(() => entityLoad("Book"), "could not read");
}
function Save(Book book) {
DatabaseOperation(() => entitySave(book), "could not save");
}
function DatabaseOperation(delegate action, string error) {
try {
action.invoke();
}
catch (any e) {
var message = "Error accessing database, " & error;
throw (type="CustomException", message=message);
}
}
Solution
Based on your example, not with CF9.
Closures are coming in CF10 which will probably allow you to do something like:
function GetAll() {
DatabaseOperation( closure(){ entityLoad("Book") } , "could not read");
}
function Save(Book book) {
DatabaseOperation( closure(){ entitySave(book) } , "could not save");
}
function DatabaseOperation(closure action, string error) {
try {
action();
}
catch (any e) {
var message = "Error accessing database, " & error;
throw (type="CustomException", message=message);
}
}
(syntax might vary, don't remember if it was exactly like that)
Alternatively, you could probably use evaluate
here, I guess...
function GetAll() {
DatabaseOperation( 'entityLoad("Book")' , "could not read");
}
function Save(Book book) {
DatabaseOperation( 'entitySave(book)' , "could not save");
}
function DatabaseOperation(string action, string error) {
try {
evaluate(action);
}
catch (any e) {
var message = "Error accessing database, " & error;
throw (type="CustomException", message=message);
}
}
Personally I would just remove the try/catch and use onError
in Application.cfc
- doesn't seem to be useful to mask the original error and instead throw a more generic one?
Update: two more possible alternatives...
Another option that currently works is to have a public wrapper function, that calls the DatabaseOperation function, passing in the name of a private function that does the actual logic like this:
function GetAll() {
DatabaseOperation( real_GetAll , Arguments , "could not read");
}
private function real_GetAll() {
entityLoad("Book")
}
function Save(Book book) {
DatabaseOperation( real_Save , Arguments , "could not save");
}
private function real_Save(Book book) {
entitySave(book)
}
function DatabaseOperation(any action, struct args , string error) {
try {
action( argumentcollection=args )
}
catch (any e) {
var message = "Error accessing database, " & error;
throw (type="CustomException", message=message);
}
}
If you don't like the idea of defining functions twice, but don't mind obscuring the API, you could set the methods to private then use onMissingMethod:
private function GetAll()
{
entityLoad("Book")
}
private function Save(Book book)
{
entitySave(book)
}
function onMissingMethod( string MethodName , struct MethodArgs )
{
if ( NOT StructKeyExists(Variables,Arguments.MethodName) )
{
throw("The method #Arguments.MethodName# was not found");
}
try
{
var Meth = Variables[Arguments.MethodName];
Meth( ArgumentCollection=Arguments.MethodArgs );
}
catch(any e)
{
var message = "Error accessing database, ";
switch(MethodName)
{
case "GetAll":
message &= "could not read";
break;
case "Save":
message &= "could not save";
break;
}
throw (type="CustomException,message=message);
}
}