If you made it here based solely on the title — good for you, you are apparently endowed with a fair bit of programming-intestinal fortitude. I figured out this little hack months ago and I just got around to writing it up over the past week or so — I’ve been busy, sue me.
I was inspired to investigate this topic by Derek Wischusen’s
post “method_missing in ActionScript.” I saw it and immediately set out to build a port of ActiveRecord to Actionscript.
I’m not convinced this is the “best” implementation for this type of functionality but after much toying about it is the best one I was able to come up with. Also, you have to remember you can’t eval anything in AS3, so in reality this is just creating Static Methods that point to an existing method that knows how to do something based on the new function’s name, but that’s pretty much what the Rails framework does so, I view that as just fine.
Goal
The initial goal was to set out and get a method like this to work:
var item:Blog = Blog.find_by_title(“Dynamically Generated Static Methods”);
The class “Blog” should have as little, preferably no, extra coding in it. It should just attempt to call that method with no burden on the programming trying to create the class “Blog.”
Strike One
The first place to start is with Derek’s post that I referenced earlier, I created a class called “ActiveRecord” that was the same as his “BaseProxy” class.
ActiveRecord v0.1
package
{
import flash.utils.flash_proxy;
import flash.utils.Proxy;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
public dynamic class ActiveRecord extends Proxy
{
public function ActiveRecord()
{
}
flash_proxy override function callProperty(method: *, ...args): * {
try {
var clazz : Class = getDefinitionByName(getQualifiedClassName(this)) as Class;
return clazz.prototype[method].apply(method, args);
}
catch (e : Error) {
return methodMissing (method, args);
}
}
protected function methodMissing(method : *, args : Array) : Object{
throw( new Error("Method Missing"));
return null;
}
}
}
The above code is taken from this “method_missing in ActionScript.”
Then I subclass “ActiveRecord” to get a class called “Blog.”
Blog.as v0.1
package
{
dynamic public class Blog extends ActiveRecord
{
public function Blog()
{
}
}
}
TestApp.mxml v0.1
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
applicationComplete="testObject()">
<mx:Script>
<![CDATA[
private function testObject():void{
var blog:Blog;
blog = Blog.find_by_name("Some Text Here.");
}
]]>
</mx:Script>
</mx:Application>
To even get the above syntax to run you have to go to Project->Properties then Flex Compiler and un-check “Enable Strict type checking.” This will allow the file to compile.
However, when you run the TestApp you will see an exception thrown, not by Derek’s code like we’d hope but by the player, “TypeError: Error #1006: find_by_name is not a function.”
Tweak the Syntax
Just to keep things on the up and up I want you to go back and change that project property back ( Project->Properties then Flex Compiler and check “Enable Strict type checking ). Then we can tweak the syntax of the TestApp.mxml line 8( blog = Blog.find_by_name… ) to be:
blog = Blog["find_by_name"](“Some Text Here.”);
We still get a type error from the player, “TypeError: Error #1006: value is not a function.” but at least our syntax compiles cleanly.
Why doesn’t this work
To answer this question you might first want to go read “Object-oriented programming in ActionScript Advanced Topics” but to summarize, the “callProperty” method Derek overrode only works on instances of the class not on the “prototype” of the class which is where the static methods live. So some how we need to access the prototype before execution. Sadly, there is no proxy method like “callProperty” for the class’s prototype, so we have to go in a more ghetto direction.
staticInitializer
The direction to move in is to use a static initializer in the Blog class to kick off building the static methods we need. I will jump straight in the flow of the working solution.
Blog v0.2
package
{
dynamic public class Blog extends ActiveRecord
{
staticInitializer(prototype.constructor);
public function Blog()
{
}
}
}
The only thing we’ve now added to Blog.as is a call to staticInitializer passing in the prototype.constructor of our current class. ( To understand what that means, check the OOP in AS article mentioned previously ). This will allow us to insert some calls before anything in this object is ever called. Think of methods like this as constructors for the Class object, not an instance of the class, but of the definition of the class — remember everything is an object. This method is actually defined in the base class ActiveRecord.as:
ActiveRecord v0.2
package
{
import flash.utils.Proxy;
import flash.utils.describeType;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
import flash.utils.flash_proxy;
// import flash.utils.flash_proxy;
public dynamic class ActiveRecord extends Proxy
{
private static var functionsToAdd:Array = ["find_by_name","find_by_id","find_all"];
protected static var methodFactory:DynamicMethodFactory;
public function ActiveRecord()
{
}
public static function staticInitializer(klass:Class):void{
var typeInfo:XML = describeType(klass);
for each(var s:String in functionsToAdd){
klass[s] = getMethod(typeInfo.@name,s);
}
}
public static function getMethod(objectName:String,methodName:String):Function{
return function(...args):Object{return endPoint(objectName,methodName,args);};
}
public static function endPoint(objectName:String,methodName:String,...args):Object{
trace("You called: " + objectName + "." + methodName + "(" + args + ")");
var klass:Class = getDefinitionByName(objectName) as Class;
var o:ActiveRecord = new klass();
return o;
}
flash_proxy override function callProperty(method: *, ...args): * {
try {
var clazz : Class = getDefinitionByName(getQualifiedClassName(this)) as Class;
return clazz.prototype[method].apply(method, args);
}
catch (e : Error) {
return methodMissing (method, args);
}
}
protected function methodMissing(method : *, args : Array) : Object{
throw( new Error("Method Missing"));
return null;
}
}
}
So you can see a few things have been added, line 13, is just a list of methods we want to add to the object, you could generated these from anything you want(hint hint). The definition for staticInitializer in line 19 is where the real stuff starts to happen. In this simple example we will loop across the functions we want to add then create a new anonymous function that remaps a bunch of parameters to our function called endPoint, which really handles the function call. This allows us to easily do anything we want based on, the class name, method name and argument list.
Tweak the Syntax, Again
If you want to make it look like a normal method call again, you can change that compiler flag back to disable strict type checking and then call your new dynamic static functions like normal.
There maybe more posts in this area, extending this concept out to create some very useful Rails-ish Actionscript libraries.