A simple define_method example

Someone on #ruby-lang wanted to know how to define a method on a class when the method is first called. Here's a quick example.
require 'rubygems'
require 'spec'

module DynamicGetter
def method_missing(name, *args)
if(@attributes.has_key? name)
self.class.class_eval do
define_method(name){ @attributes[name] }
end
self.send(name, *args)
else
super
end
end
end

describe DynamicGetter do
before(:each) do
# Reference the class used in the specs
# as an ordinary variable rather than a
# named constant (@ooga instead of Ooga)
# so that it can be created afresh for
# each spec.
# If we did class Ooga, the second spec
# would fail because the first spec already
# created the method #woot.

@ooga_klass = Class.new(Object)
@ooga_klass.class_eval do
include DynamicGetter

def initialize(attributes = {})
@attributes = attributes
end
end
end

it "should know how to add a method to a class on first call" do
o = @ooga_klass.new(:woot => 5)
o.should_not respond_to(:woot)
o.woot.should == 5
o.should respond_to(:woot)
end

it "should raise a method not found exception if the attribute isn't present" do
lambda{ @ooga_klass.new.woot }.should raise_error(NoMethodError)
end
end
Another example is the Rails find_by_* methods, which are defined the first time you call them.
If, for some reason, you want to apply the example I've given on a per-instance basis, look at this post.

Related posts:

2 comments:

Josh said...

the raising of NoMethodError can be replaced with a call to super() and ruby will throw the error for you plus less code to maintain :)

Unknown said...

There is much wisdom in what you say. It is done.