Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the New York ColdFusion User Group (Jan. 2009) with:

Learning ColdFusion 8: OnMissingMethod() Event Handler

Posted by Ben Nadel
Tags: ColdFusion

ColdFusion 8 has added more error handling in the form of the ColdFusion component OnMissingMethod() event handler. The OnMissingMethod() event handler moves error handling from the calling environment into the target component environment. How it works is that if you call a method on a given ColdFusion component and that method does not exist, ColdFusion then checks the target CFC to see if the OnMissingMethod() method is defined. If it is defined, ColdFusion then invokes it and passes in the name of the originally invoked method and its arguments. If the OnMissingMethod() is not defined, then ColdFusion allows the exception to bubble up into the calling environment where it may or may not be handled by something like a CFTry / CFCatch block.

The OnMissingMethod() function is quite simple. It is passed two argument that are guaranteed to exist: MissingMethodName and MissingMethodArguments.

  • <cffunction
  • name="OnMissingMethod"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Handles missing method exceptions.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="MissingMethodName"
  • type="string"
  • required="true"
  • hint="The name of the missing method."
  • />
  •  
  • <cfargument
  • name="MissingMethodArguments"
  • type="struct"
  • required="true"
  • hint="The arguments that were passed to the missing method. This might be a named argument set or a numerically indexed set."
  • />
  •  
  • <!--- Dump out the arguments. --->
  • <cfdump
  • var="#ARGUMENTS#"
  • label="Missing Method Arguments"
  • />
  •  
  • <cfabort />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>

In the example above, I am simply CFDump'ing out the ARGUMENTS scope, however, here you could do some sort of logging or some sort of event continuation. If the original method was invoked without arguments then on MissingMethodArguments will still exist but will be an empty struct.

To play around a little bit with this stuff, let's build a Test.cfc that contains nothing by the OnMissingMethod() event handler:

  • <cfcomponent>
  •  
  • <cffunction
  • name="OnMissingMethod"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Handles missing method exceptions.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="MissingMethodName"
  • type="string"
  • required="true"
  • hint="The name of the missing method."
  • />
  •  
  • <cfargument
  • name="MissingMethodArguments"
  • type="struct"
  • required="true"
  • hint="The arguments that were passed to the missing method. This might be a named argument set or a numerically indexed set."
  • />
  •  
  • <!--- Dump out the arguments. --->
  • <cfdump
  • var="#ARGUMENTS#"
  • label="Missing Method Arguments"
  • />
  •  
  • <cfabort />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

Here, we are basically copying our OnMissingMethod() function we defined earlier, into the Test.cfc. Now, let's call a method on the Test.cfc that doesn't exist:

  • <!--- Create a Test instance. --->
  • <cfset objTest = CreateObject( "component", "Test" ) />
  •  
  • <!---
  • Call a method that doesn't exist but do not
  • pass in any arguments.
  • --->
  • <cfset objTest.Blam() />

This gives us the following CFDump output:


 
 
 

 
Calling Undefined Method With No Arguments To Trigger OnMissingMethod() Event Handler  
 
 
 

As you can see, ColdFusion passes in Blam as the target method name and an empty struct as the missing method arguments since we didn't use any invocation arguments when calling Blam.

When we call a missing method with arguments, ColdFusion gives us the option to use ordered arguments as well as named arguments. Both of these styles work just fine. Here, we are calling the Blam method with ordered arguments:

  • <!--- Create a Test instance. --->
  • <cfset objTest = CreateObject( "component", "Test" ) />
  •  
  • <!---
  • Call a method that doesn't exist and pass in a set
  • of ordered arguments.
  • --->
  • <cfset objTest.Blam(
  • "Foo",
  • "Bar"
  • ) />

This results in the following CFDump:


 
 
 

 
Calling Undefined Method With Ordered Arguments To Trigger OnMissingMethod() Event Handler  
 
 
 

As you can see, the MissingMethodArguments struct here (which is really just the coldfusion.runtime.ArgumentCollection that was passed to the target method) is an index-based structure. But then, when we call the Blam method with named arguments:

  • <!--- Create a Test instance. --->
  • <cfset objTest = CreateObject( "component", "Test" ) />
  •  
  • <!---
  • Call a method that doesn't exist and pass in a set
  • of named arguments.
  • --->
  • <cfset objTest.Blam(
  • Too = "Sexy",
  • How = "Good"
  • ) />

... we get the following CFDump output:


 
 
 

 
Calling Undefined Method With Named Arguments To Trigger OnMissingMethod() Event Handler  
 
 
 

Here, our MissingMethodArguments struct works like a standard struct. So basically, the summary here is that the MissingMethodArguments works exactly like the ColdFusion arguments scope that you would find in any other method, well, cause that is exactly what it is.

Now, there is nothing special about the OnMissingMethod() event handler other than the fact that ColdFusion will leverage it for use in error handling. There is nothing about it that prevents us from calling the OnMissingMethod() method directly:

  • <!--- Create a Test instance. --->
  • <cfset objTest = CreateObject( "component", "Test" ) />
  •  
  • <!---
  • Call the missing method function directly. Since
  • all of its arguments are required, we have to create
  • fake paramters.
  • --->
  • <cfset objTest.OnMissingMethod(
  • MissingMethodName = "Blam",
  • MissingMethodArguments = StructNew()
  • ) />

Notice that we are passing in fake method parameters for MissingMethodName and MissingMethodArguments. Since I have defined these arguments as being required, we must pass them in. Technically, I could just make them NOT required, but I feel like they are something that should be there all the time. Running the above code, we get the following CFDump output:


 
 
 

 
Calling OnMissingMethod() Directly With Fake Arguments  
 
 
 

Because ColdFusion allows us the ability to call this method directly, it gives us an opening to do some performance testing. The OnMissingMethod() event handler is really some sort of error handling and all error handling has some degree of overhead. The good news is that OnMissingMethod() probably does not actually deal with exceptions (which have a LOT of overhead); most likely, ColdFusion is just checking to see if the method exists as a key in the CFC before it attempts to invoke it.

To test some speed comparisons, we are going to remove the CFDump and CFAbort from the OnMissingMethod() event method. This way, when invoked, the method will just return out and do basically nothing else. Then, we are going to compare the speed of invoking an undefined method to the speed of calling the OnMissingMethod() directly. This should allow us to isolate the overhead associated with the redirected invocation.

  • <!--- Create a Test instance. --->
  • <cfset objTest = CreateObject( "component", "Test" ) />
  •  
  •  
  • <!---
  • Set the number of iterations we are going to use
  • to test the invokation speeds.
  • --->
  • <cfset intCount = 10000 />
  •  
  •  
  • <!---
  • We are going to start out by calling a method that
  • does not exist. This will get ColdFusion to invoke
  • the OnMissingMethod() event handler. Get the tick
  • count before we start the test.
  • --->
  • <cfset intStart = GetTickCount() />
  •  
  • <!--- Loop over the target number of iterations. --->
  • <cfloop
  • index="intI"
  • from="1"
  • to="#intCount#"
  • step="1">
  •  
  • <!--- Invoke the undefined method. --->
  • <cfset objTest.Blam() />
  •  
  • </cfloop>
  •  
  • <!--- Output the time difference. --->
  • <p>
  • Indirect:
  • #(GetTickCount() - intStart)# Milliseconds
  • </p>
  •  
  •  
  • <!---
  • Now, we are going to compare that to the speed of
  • calling the OnMissingMethod() function directly. Get
  • the tick count before we start the test.
  • --->
  • <cfset intStart = GetTickCount() />
  •  
  • <!--- Loop over the target number of iterations. --->
  • <cfloop
  • index="intI"
  • from="1"
  • to="#intCount#"
  • step="1">
  •  
  • <!--- Invoke the missing method function. --->
  • <cfset objTest.OnMissingMethod(
  • MissingMethodName = "Blam",
  • MissingMethodArguments = StructNew()
  • ) />
  •  
  • </cfloop>
  •  
  • <!--- Output the time difference. --->
  • <p>
  • Direct:
  • #(GetTickCount() - intStart)# Milliseconds
  • </p>

I ran this several times (each of which ran 10,000 iterations per test type) and found the following Millisecond results:

Indirect:
201, 200, 211, 210, 210, 201

Direct:
390, 300, 290, 301, 291, 300

As you can see, the direct method invocation was actually slower! I assume that this is due to the fact that we were explicitly creating the arguments that OnMissingMethod() excepted. I didn't like this, so I then went in and updated the arguments so that they were NOT required. Then, I re-ran the test, performing both loops by calling a method with NO arguments:

  • <!--- Invoke the undefined method. --->
  • <cfset objTest.Blam() />
  •  
  • ......
  •  
  • <!--- Invoke the missing method function. --->
  • <cfset objTest.OnMissingMethod() />

Without the overhead of manually creating the arguments, I get these for speed tests:

Indirect:
200, 200, 210, 200, 210, 200

Direct:
180, 170, 170, 181, 170, 180

Ok, that makes a bit more sense. It should be more efficient to call the OnMissingMethod() directly since there is less logic taking place. However, from both tests, we can see that the OnMissingMethod() adds no significant overhead to method invocation at all. This is very good news.

As a quick note, if you try to call a Private method from outside of the target ColdFusion component, the OnMissingMethod() function fires just the same as if you called a public method that did not exists.

When ColdFusion invokes the OnMissingMethod() event handler, it does not disrupt the flow of the page in any way. If you call an undefined method and expect it to return a value, AND you have an OnMissingMethod() then returns a value, the calling page will act just like the target method returned that value. To demonstrate, if we modify our OnMissingMethod() event handler to return the name of the target function:

  • <cffunction name="OnMissingMethod">
  • <!--- Code abbreviated.... --->
  •  
  • <!--- Return out. --->
  • <cfreturn ARGUMENTS.MissingMethodName />
  • </cffunction>

... and then we try to invoke an undefined method:

  • #objTest.BooYaa()#

... we get the following output:

BooYaa

As you can see, the page acts just like BooYaa() was a method that did indeed exist and returned a valid value.

I think the point of the OnMissingMethod() ColdFusion component event handler is really to more gracefully handle exceptions in terms of logging and to aide in debugging. But, let's face it, that's not very interesting. Let's see if we can leverage the highly dynamic behavior that this method gives us to do some stuff that is a little bit more exciting.

I DO NOT RECOMMEND that people use the OnMissingMethod() for the following example - this was JUST FOR FUN!! We are going to basically "extend" an object by wrapping around it. This is the ugly love-child of inheritance and composition (these two programming constructs are in the same family and shoudl definitely NOT be bumpin' uglies). If you want to do something like this, I would much rather you actually build a Java class that extends a target Java class or something to that effect... As much as I might like to see it, my spine tingles a little bit to think that people might actually use this in some sort of Business Object in the way that I am about to demonstrate (this is all gut feeling - not an educated reaction!!!!).

Ok, that being said, let's say for arguments sake, I've always hated the Java String object because it doesn't have a "Contains" method. I love the rest of the String object, but I just really want it to have a Contains() method that takes a regular expression and returns the index of the first regular expression match (this is what REFind() does). ColdFusion 8's OnMissingMethod() event handler gives me the opportunity to wrap a Java String object inside of a ColdFusion component and then selectively add or override methods.

Here is my String ColdFusion component wrapper:

  • <cfcomponent
  • output="false"
  • hint="This is a crude ColdFusion-based decorator for the java.lang.String object.">
  •  
  • <!---
  • Run the pseudo constructor to set up data
  • structures and default values.
  • --->
  •  
  • <!---
  • Create a target object. This is the Java String
  • that the outside world is going to be "aiming"
  • for with their actions. I am casting to string here
  • just to ensure that we definitely have a string.
  • --->
  • <cfset VARIABLES.Target = JavaCast( "string", "" ) />
  •  
  •  
  • <cffunction
  • name="Init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Initializes the string wrapper.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Value"
  • type="string"
  • required="false"
  • default=""
  • />
  •  
  • <!--- Set the default value. --->
  • <cfset VARIABLES.Target = ARGUMENTS.Value />
  •  
  • <!--- Return This scope. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="Contains"
  • access="public"
  • returntype="numeric"
  • output="false"
  • hint="Tests to see if the given regular experssion exists somewhere in the target string.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="RegEx"
  • type="string"
  • required="true"
  • />
  •  
  • <!--- Return the REFind() on the string. --->
  • <cfreturn REFind( ARGUMENTS.RegEx, VARIABLES.Target ) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="Get"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="Gets the internal string value.">
  •  
  • <!--- Return the string value. --->
  • <cfreturn VARIABLES.Target />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="OnMissingMethod"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Handles missing method exceptions.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="MissingMethodName"
  • type="string"
  • required="true"
  • hint="The name of the missing method."
  • />
  •  
  • <cfargument
  • name="MissingMethodArguments"
  • type="struct"
  • required="true"
  • hint="The arguments that were passed to the missing method. This might be a named argument set or a numerically indexed set."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Get the array of argument keys. --->
  • <cfset LOCAL.Keys = StructKeyArray(
  • ARGUMENTS.MissingMethodArguments
  • ) />
  •  
  •  
  • <!--- Check to see if there are any arguments. --->
  • <cfif NOT ArrayLen( LOCAL.Keys )>
  •  
  • <!--- Just return the method invocation. --->
  • <cfreturn Evaluate(
  • "VARIABLES.Target.#ARGUMENTS.MissingMethodName#()"
  • ) />
  •  
  • <!---
  • If we have arguments, we might have indexed
  • or numeric. Since this CFC wraps around a Java
  • object, they should all be numbered, but I will
  • check anyway.
  • --->
  • <cfelseif IsNumeric( LOCAL.Keys[ 1 ] )>
  •  
  • <!---
  • We are going to have to build up the arguments
  • string used in the evaluation.
  • --->
  • <cfset LOCAL.Args = "" />
  •  
  • <!--- Loop over the keys. --->
  • <cfloop
  • index="LOCAL.KeyIndex"
  • from="1"
  • to="#ArrayLen( LOCAL.Keys )#"
  • step="1">
  •  
  • <!--- Build up the ORDERED parameter. --->
  • <cfset LOCAL.Args &= (
  • IIF(
  • (LOCAL.KeyIndex GT 1),
  • DE( "," ),
  • DE( "" )
  • ) &
  • "ARGUMENTS.MissingMethodArguments[ #LOCAL.KeyIndex# ]"
  • ) />
  •  
  • </cfloop>
  •  
  • <!---
  • Return the evaluated method with the passed
  • in, ordered arguments.
  • --->
  • <cfreturn Evaluate(
  • "VARIABLES.Target.#ARGUMENTS.MissingMethodName#(" &
  • LOCAL.Args &
  • ")"
  • ) />
  •  
  • <!--- We have arguments, but they are named. --->
  • <cfelse>
  •  
  • <!---
  • We are going to have to build up the arguments
  • string used in the evaluation.
  • --->
  • <cfset LOCAL.Args = "" />
  •  
  • <!--- Loop over the keys. --->
  • <cfloop
  • index="LOCAL.KeyIndex"
  • from="1"
  • to="#ArrayLen( LOCAL.Keys )#"
  • step="1">
  •  
  • <!--- Build up the NAMED parameter. --->
  • <cfset LOCAL.Args &= (
  • IIF(
  • (LOCAL.KeyIndex GT 1),
  • DE( "," ),
  • DE( "" )
  • ) &
  • "#LOCAL.Keys[ LOCAL.KeyIndex ]#=" &
  • "ARGUMENTS.MissingMethodArguments[ #LOCAL.KeyIndex# ]"
  • ) />
  •  
  • </cfloop>
  •  
  •  
  • <!---
  • Return the evaluated method with the passed
  • in, ordered arguments.
  • --->
  • <cfreturn Evaluate(
  • "VARIABLES.Target.#ARGUMENTS.MissingMethodName#(" &
  • LOCAL.Args &
  • ")"
  • ) />
  •  
  • </cfif>
  •  
  • <!--- Return out. --->
  • <cfreturn ARGUMENTS.MissingMethodName />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="Set"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Sets the internal string value and return a pointer to the wrapper.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Value"
  • type="string"
  • required="false"
  • default=""
  • />
  •  
  • <!--- Set the target value. --->
  • <cfset VARIABLES.Target = ARGUMENTS.Value />
  •  
  • <!--- Return This scope. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  • </cfcomponent>

Notice that it has a Contains() method defined. Notice also, that it is wrapping a string variable at VARIABLES.Target. Now that I have this in place, I can call standard Java method:

  • <!--- Create a ColdFusion string wrapper. --->
  • <cfset objString = CreateObject(
  • "component",
  • "String"
  • ).Init( "This is wicked sweet!" )
  • />
  •  
  • <!---
  • Call a standard java method. IndexOf() will return
  • the zero-based index of the first matching string index.
  • --->
  • #objString.IndexOf( "is" )#

This gives me the following output:

2

... because "is" is at index 2, ending the word "This" ([0]T[1]h[2]i[3]s). But, notice that we didn't have an IndexOf() method defined in the String.cfc. Because it doesn't exist, when we call it, ColdFusion actually fires our OnMissingMethod() event handler. Inside of that handler, we then build up an arguments list in order to dynamically evaluate the target Java method. Notice that when we are building our arguments list, we are checking to see if we have ordered arguments or named arguments; this is because Java methods cannot take named arguments, only ordered arguments. Also, there is nothing that says CFC methods have to be invoked with named arguments - therefore we should be checking for both types.

But, since we actually DO have a Contains() method defined, we can call that directly without any OnMissingMethod() shenanigans:

  • <!--- Create a ColdFusion string wrapper. --->
  • <cfset objString = CreateObject(
  • "component",
  • "String"
  • ).Init( "This is wicked sweet!" )
  • />
  •  
  • <!---
  • Find the index of the first instance of the
  • given regular expression.
  • --->
  • #objString.Contains( "\b\w{2}\b" )#

This gives me the following output:

6

... because the word "is" is at the one-based index of the original string ([1]T[2]h[3]i[4]s[5] [6]i[7]s).

Ok, so before any of you look at this and think "cool!", let me remind you that this was just for fun and should be in absolutely no way what so ever a representation of a recommendation or best practice. Java is a wicked dynamic language that handles amazing reflection, and yet, I am building arguments via string concatenation. I am simply not knowledgeable enough to build reflective invocation any better. But, I don't have time to look it up and learn it now, and even if I did look up reflection, I would have no idea how to translate the typeless ColdFusion arguments into a typed array of Java data types used in reflection (that just made my brain hurt).

As you can see, ColdFusion 8's OnMissingMethod() event handler can be a useful tool. It can also give us just enough power to be a danger to ourselves. Personally, other than logging, I don't really see the benefit of catching errors internally to a ColdFusion component as opposed to letting them bubble up to the calling environment. One thing I was weary of, but didn't try because I don't want to crash HostMySite.com's beta account is the issue of recursion; what happens if you OnMissingMethod() event handler tries to invoke a method that doesn't exist? Would this create some sort of infinite invocation cycle? Or is ColdFusion 8 smart enough to not let that happen? I am gonna give ColdFusion 8 the benefit of the doubt because I do know that errors thrown in the OnError() Application event handler will actually just thrown an uncaught error, not re-fire the OnError() event method. I assume that CF8 is smart enough to do the same thing when it fired the OnMissingMethod() event.




Reader Comments

I'm not really sure what use the onMissingMethod() was intended for. You mention error handling, but I can't see how that's useful. You should definitely be looking up the documentation before you call a method on an object, and if for some reason you don't, what should happen? It should throw an error, right? How can you possibly recover gracefully from someone asking you to do something you don't know how to do? The only possible valid use I can think of is some kind of polite error that says "I don't know how to do that", but I don't see that as more helpful than "method not found" or whatever the default CF error is.

I foresee this method being abused as a generic getter and setter, i.e. a developer doesn't want to write/generate all the getters and setters, so they just use onMissingMethod() to get or set private variables based on the missing method name, and only write the getters and setters that have a bit more logic to them. e.g. getName() doesn't exist, so omMissingMethod just returns the internal Name variable. But getJob() exists because that takes some specialized logic to retrieve, so the coder will actually write that function out.

In the process, or course, we lose typing and required attributes for the arguments passed in.

Reply to this Comment

@Jeremy,

I agree with just about everything you said. I think documentation is a must! That's why I never understand why people get all hot-and-bothered over things like Interfaces. Also, I fear that this is something that will be used and abused. That is why I tried to stress very hard that my example for the CFC wrapper was JUST FOR FUN and that no one should be doing that.

Reply to this Comment

Smalltalk has #doesNotUnderstand and Ruby has missing_method - that's where ColdFusion's onMissingMethod() really comes from. It isn't really about error handling - it's about providing a simple way to create dynamic proxy objects.

As such it provides a way to implement get/set methods without actually having to write them. It also provides a way to implement AOP without a lot of dynamic machinery (because you don't have to synthesize a new object for every proxied object).

I'll be blogging a lot more about this in due course but this is really a key piece of functionality in allowing ColdFusion to leverage its dynamic nature, the way that Smalltalk and Ruby do, and provide features that are impossible in Java.

Reply to this Comment

@Sean,

So, just so we a clear, are you saying that OnMissingMethod() was designed for the same thing I was saying people shouldn't use it for?

Reply to this Comment

Well, your example could be coded in a much more efficient way (it does not need to use evaluate() for example!).

<cfinvoke object="#variables.target#" method="#arguments.missingMethodName#" returnvariable="result" />
<cfif isDefined("result")>
<cfreturn result />
</cfif>

But, yes, onMissingMethod() was primarily added to allow ColdFusion to support dynamic proxies in the same way that Ruby and Smalltalk do.

For example, your base component could use onMissingMethod() to implement getFoo() / setFoo() methods without you actually writing them and it's deliberately efficient enough that it's reasonable to do so.

It was not added as an error handler.

Reply to this Comment

@Sean,

Well, don't I feel foolish :) I try not to make it a habit to give the completely opposite advice. Do you know if there is a best-practices document on how to use something like this (either for ColdFusion or SmallTalk or Ruby)? I would like to see how people suggest that they use this, and if there are trade-offs.

As for more efficient than Evaluate(), I could not get the CFInvoke to work on Java objects (my example uses the Java string object). From the CF8 docs it looks like it really was designed to work on Components and web services. I cannot get Java methods to work with named arguments, even when indexed, eg:

StringBuffer.Append( 1 = "blam" )

I kept going over it in my mind and I just couldn't come up with anything other than Evaluate()... which was part of why I said this was a horrible idea :)

Reply to this Comment

@Sean

That's actually method_missing. ;)

@Ben

The idea is that you can create objects that respond to method calls which may not really exist in the component definition.

This is really useful for something like Reactor, Transfer that formerly had to resort to code generation and then writing it out to disk, which can now just use a generic component instance and dispatch the method calls using onMethodMissing().

Python allows similar things using __getattr__ and __setattr__, ruby has method_missing, php has __call, __get and __set.

Adding onMethodMissing() I think is probably the first really big step in a while in terms of bringing CF into future in terms of language semantics.

Now we just need onPropertyGet() and onPropertySet() so we can deal with the last two cases...

Reply to this Comment

@Elliott, true, you define method_missing but most of the Ruby literature refers to missing_method either in the context of a class or a pattern. And missing_method is easier to say :)

@Ben, I hadn't tried cfinvoke with Java methods. I'm a little surprised it doesn't work although may be that use case wasn't considered... In which case, it looks like evaluate() would be necessary. For the CFC case (delegating to a CFC), evaluate() is definitely not necessary tho' and that was the primary use case. As Elliott says, Reactor, Transfer and ColdSpring can all benefit from this (and ColdSpring 2.0 will implement AOP this way and therefore will be CF8-specific).

Reply to this Comment

@Ben, I forgot to add: I will be writing a lot more about onMissingMethod() since I was the one that requested it :)

Reply to this Comment

There are so many possible uses of this. And I can't believe I didn't notice this new feature until now.

Ruby on Rails uses dynamic finders in its Active Record implementation which does make the code feel more natural and keeps things easy to read. You can do things like find_by_name("Bob") even when no such method exists.

For ColdFusion, the most obvious application is using dynamic getters and setters based around the cfproperty tag, so getters and setters are automatically available by inspecting the cfproperty metadata.

I'm wondering: if onMissingMethod is implemented in Application.cfc, does ColdFusion call it for onRequestStart et. al. when these aren't explicitely defined? This may have some nasty (and interesting) implications.

Reply to this Comment

I've started blogging about onMissingMethod() now. I just posted an example showing how to wrap an object and call its methods either directly or wrapped in a cftransaction tag, depending on the method name (very similar to the Ruby approach of dynamically determined find_by methods).

I also showed how to use onMissingMethod() in Mach-II to provide broadcast-listener semantics, similar to Model-Glue.

In the upcoming ColdFusion 8 issue of FAQU, I have an article that talks about various possibilities with onMissingMethod() - including dynamic get/set.

Some of what I'm doing with it relies on being able to cleanly call methods on delegated objects so I'm using a modified component.cfc containing a few utility methods. I'll make that available shortly.

Reply to this Comment

@Scott, no, onMissingMethod() in Application.cfc does not get triggered for onRequestStart() etc - because ColdFusion explicitly looks for those method names.

Reply to this Comment

@Ben,

I know it's been two months since the last comment, but I just had to ask: what did you mean when you wrote "I never understand why people get all hot-and-bothered over things like Interfaces".

Are you referring to the Java language construct or the general idea of coding to interfaces?

Reply to this Comment

@Dave,

I think I was referring the importance of locking a programmer into a specific API. Really, the way I see Interfaces is creating a compile-time contract as opposed to creating a run-time contract. If a programmer doesn't know what methods are available, then it doesn't matter much what the contracts are - the program will break, either at compile time or at run time.

And, since ColdFusion is such a hugely dynamic language, you can actually take a CFC instance and completely alter its functionality at run time, regardless of any Interface that was tied to it.

I guess, bottom line, a programmer should know what methods are available on an object before he/she uses it. If that is the case, then the Interface really adds nothing beneficial in a dynamically typed language, unless of course, you are using it for parameter-type-checking, but even then, I know a number of people who put "any" for all parameters so that type-checking doesn't slow things down.

What I am really trying to say is that I don't see what Interfaces add in terms of benefits.

Reply to this Comment

I can't get onMissingMethod to work with my CFC when it is accessed through a URL. It works fine when invoked from CF but when I try to invoke it through a URL I get:

The method [methodname] was not found in component [component path].

Reply to this Comment

@Steve,

Interesting. I have not even thought of doing this. Is your OnMissingMethod() function set to "remote" access? Not sure if that will help. Because this is a remote call, it's possible that this will not work.

Reply to this Comment

I tried adding access=remote and it didn't help. I am writing a REST web service so I didn't want it to return all that CF Error stuff to the requester if someone made a call to a method that didn't exists. I wanted to just return a string with an error.

Reply to this Comment

@Steve,

Hmmm. Not sure that I know any good advice. My only comment is that if someone calls the *wrong* web service method, you might want to return an error. I don't think errors are bad in that respect.

Reply to this Comment

That's the way it will have to be I guess. The methods of my web service are supposed to return JSON formated structures so I hate to blast them with a bunch of html that makes up the CF error response. Oh well.

Reply to this Comment

I'm actually using onMissingMethod with evaluate() in production and I don't see any drawbacks. Cloud you please elaborate on your concerns about this?

I actually wrap a mongodb's DBCursor into a custom component to extend it's functionality directly from coldfusion.

As I come from the javascript world where this technique is extremely common (see jQuery) for me is quite natural, although I'm worried about how much overhead produces evaluate().

  • //
  • // Delegates the native methods of the MongoDBObject
  • // to the object itself, and wraps it again into
  • // this component.
  • //
  • // - **@param** *method* String representing the native method name
  • // - **@param** *args* Struct representing the given arguments
  • // - **@return** _Instance/Value_
  • //
  • function onMissingMethod(required string method, required struct args){
  • var argkeys = structkeyarray(args);
  • var _unevaled = [];
  • for (var k = 1; k lte arraylen(argkeys); k++){
  • arrayappend(_unevaled, "args['#k#']");
  • }
  • var _object = evaluate("this.object['#method#'](#arraytolist(_unevaled)#)");
  • if (isInstanceOf(_object,'com.mongodb.DBCursor')){
  • return createObject('service.cfc.mongodb.MongoWrapper').init(_object);
  • } else {
  • return _object;
  • }
  • }

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.