Posts tagged ‘metaprogramming’

JigSaw – Part I

The option you missed

JigSaw is a project that I’m currently working on. It is still under heavy development and far from being a release candidate. It is supposed to become an all in one config file or option parser and option provider.

What I miss in Ruby out of the box is an easy way to handle the configuration of your application. You have lots of options but you have to write most of the code yourself. My vision is to set up a possible configuration either with an application config file or by passing options to JigSaw and let it handle all the heavy stuff like reading the user configuration, validation and an easy way to access the provided options.

I like the way Javascript provides for accessing hashes with the dot-operator. Like

var fruits = {
  green:'apple',
  yellow:'banana'
};

alert(fruits.yellow)

This notation is easy to read, to understand and to write. I do not know what it is like on an english keyboard, but on a german keyboard it is a pain to write something like this: fruits['curved']['yellow'].... Instead I want to be able to type fruits.curved.yellow and get my banana. This is easily done in Javascript as you have seen, in Ruby it is a bit difficult.

The dot-operator in Ruby is reserved for methods. For example:

class Fruits
  def yellow
    'banana'
  end
end

f = Fruits.new
f.yellow == 'banana'

This works but is very far from where we want to be.

class Fruits
  attr_accessor :yellow, :green
  def initialize
    @yellow = 'banana'
    @green = 'apple'
  end
end

f = Fruits.new
f.yellow != f.green  #bananas are not apples

This gets a bit closer. But still I do not want to have a class, where all methods are predifined. Ruby is a dynamic language. So we can add methods to a class at run time. We can define instance variables and attr_accessors on the fly and we can then access the instance variables with their accessor methods. Sounds easy, lets do it.

class Options
  def initialize
    @stored_options = []
  end
  
  #get the stored options (only names no values)
  def get_options
    @stored_options
  end
  
  #set the class to be immutable - no options can be added after this method is invoked.
  #the function applies recursive to all children
  #all variables that have not recieved a value are set to nil
  def set_immutable
    self.class.class_eval do
      define_method :method_missing do |meth, *args|
        raise NoOptionsEntry, "no option #{meth} defined"
      end
    end
    set_unset_values_to_nil
  end

  private
  #set option accessor methods
  def method_missing(meth, *args)
    self.class.class_eval do
      attr_accessor meth.skip_equal
    end
    @stored_options << meth.skip_equal unless @stored_options.include? meth.skip_equal
    if args.empty?
      __send__("#{meth}=", Options.new)
    else
      __send__(meth, *args)
    end
  end
  
  #set options to nil that have been initialized but have not recieved a value
  def set_unset_values_to_nil
    @stored_options.each do |o|
      if __send__(o).class == JigSaw::Options::Options
        if __send__(o).get_options.empty?
          __send__("#{o}=", nil)
        else
          __send__(o).set_immutable
        end
      end
    end
  end
  
end

This is an excerpt of the options class (you can find the complete on github). The important part is done in the method_missing. When a class method is called that does not exist, it creates attribute accessors for an instance variable named after this missing method and it applies the value, that is supplied as an argument, using the newly created accessor method. For example op.awesome = true would create the accessor methods for awesome and apply the value true to the instance variable awesome.

But this is only one half of the story. We also want to be able to chain options, like op.green.apple = true. This is done by checking if the arguments given to the method are empty. If they are, an options tree is build by setting the return value of the current method to be a new option class, that will hold the sub-options of the current. This goes on recursively until an argument is not empty.

After initialization of the options tree, it is recommended to make it immutable. This sets all variables that have no value set to nil recursively and does not allow any modification of the tree. If not done, you can not distinguish between an unset option or an option that should not exist due to your configuration.

First I thought that this kind of class manipulation could be a bit slow, but my benchmarks showed that setting about 1000 options – even recursively in two levels – was pretty fast. About 0.1s for setting 1000 values on the first level, and about 0.2s for the second level. As you probably will not have 1000 options, I believe that the time is neglect-able. Data access performance is no problem either, because the classes are not modified while accessing their instance variables, they just return values. The manipulation is only performed, when first accessed.

This initialization process is of course a bit complicated and should not be bothering the developer, therefore the JigSaw gem will have a setter class for converting a hash or a yaml file easily to an options class with initialized accessor methods. This will be part of a future post, because the setter part is not ready yet.

October 4, 2008 at 8:15 pm Leave a comment


Recent Posts

Archives

del.icio.us

Feeds