Friday, November 8, 2013

Magic Coffee

One pattern that I wanted to implement for exspresso is used in php frameworks,  wherein PHP Magic is used redirect missing property or method references back to the main controller. For an example, take a look at this CodeIgniter Model class.

PHP magic uses __get, _set, and __call. These work much like Ruby's missing_method, and it would be nice to use the same kind of 'magic' in coffeescript, which means javascript.

So how do I implement this in javascript? Catching missing method exceptions won't be scalable. I need to find a different path to the same ends. I know that javascript gives me a different way to trap a missing method. Whenever a method or property is not found, the prototype chain is searched. Coffeescript uses this to implement class inheritance. I also know that the missing methods we're looking for will all be on the current controller instance.

If I can just insert the controller object into the prototype chain of each child object, I will get the same result. This is a simple version of technique I'm using in exspresso:

#
# Magic
#
# Dependency injection via prototype.
#
#
# Get all methods and properties in the prototype chain
#
metadata = (klass) ->

  chain = []
  props = {}
  proto = klass:: # starting point in the chain

  # Build an inheritance list
  until proto is Object::
    chain.push proto
    proto = Object.getPrototypeOf(proto)

  # Reverse list to process overrides in the correct order
  for proto in chain.reverse()
    if proto isnt Object::
      # Build the inherited properties table
      for key in Object.getOwnPropertyNames(proto)
        props[key] = Object.getOwnPropertyDescriptor(proto, key)

  props

magic = (parent, klass, args...) ->

  # clone the object with all properties
  child = Object.create(parent, metadata(klass))
  # call the constructor
  klass.apply child, args
  child

#
# Main Controller Class
#
class Controller

  constructor: ->
    @date = new Date()
    @user = magic(@, Model, 'Zaphod')

  println: (msg) ->
    console.log msg

#
#
#
class Model

  constructor: (name) ->
    @name = name

  hello: () ->
    @println @name + " " + @date

#
# Create the applications main controller
#
controller = new Controller()
#
# Call the models hello method.
# Missing methods and properties fallback to the controller.
#
controller.user.hello()
(updated: to use google prettify rather than embedded gists. It makes it easier to focus on the code)

No comments:

Post a Comment