RailsConf08: Meta-programming Ruby for Fun and Profit

I’m currently at RailsConf and it is fantastic. I’ve met a good number of interesting people and attended some interesting sessions.

Ruby Internals

This morning Neal Ford and Patrick Farley gave a great session on Meta-programming Ruby for Fun and Profit. The slides should eventually show up
here.

Particularly interesting was Patrick’s portion about the internals of Ruby’s method dispatch and how it relates to inheritance, mixins, and the object’s eigenclass. The basic idea is that when you call a class-method in Ruby the eigenclass (or metaclass) has a parallel inheritance structure to the actual class. I may write more about this with diagrams after Patrick posts the slides, but the the understanding of the system leads to this koan-like ruby truth:

The super-class of the meta-class is the meta-class of the super-class

Don’t worry if it isn’t clear right away. Reid and I
attended the session and then discussed it over launch and it still took a while to sink in. In the meantime, checkout _why’s article Seeing Metaclasses Clearly.

Use class_eval instead of reopening a class

Patrick gave another very handy tip when dealing with meta-programming. He mentioned that using class_eval is much safer than re-opening a class and defining a method. This is because you don’t always know when files are loaded and when you open a class you may be defining it without realizing it. When you use class_eval you are using an existing constant. For example:

 
# warning! this could be defining ExistingKlass and you don't even know it!
# then the original ExisitingKlass will just overwrite your method
class ExistingKlass
  def change_a_method
    # ...
  end
end
 
# do this instead as it will not try to define ExistingKlass. class_eval
# guarantees the class already exists
ExistingKlass.class_eval do 
  def change_a_method
    # ...
  end
end

A Recorder

Neal shared with us a very interesting class that records the messages sent to it`and can play them back later on. Here’s the code for it:

# recorder.rb
class Recorder
  def initialize
    @messages = []
  end
 
  def method_missing(method, *args, &block)
    @messages << [method, args, block]
  end
 
  def play_back_to(obj)
    @messages.each do |method, args, block|
      obj.send(method, *args, &block)
    end  
  end
end
 
# recorder_test.rb
class TestRecorder < Test::Unit::TestCase
  def test_recorder
    r = Recorder.new
    r.sub!(/Java/) { "Ruby" }
    r.upcase!
    r[11, 5] = "Universe"
    r << "!"
 
    s = "Hello Java World"
    r.play_back_to(s)
    assert_equal "HELLO RUBY Universe!", s
  end
end

I haven’t fully processed the power of this idea yet. However, I feel like it could have some application in genetic programming.

Tabula Rasa, the Recorder, and DSLs

Neal pointed out that one of the issues with Recorder is the following:

# recorder_test.rb
class TestRecorder < Test::Unit::TestCase
  def test_recorder
    r = Recorder.new
    r.upcase!
    r.freeze
 
    s = "hi nate"
    r.play_back_to(s)
    assert_equal "HI NATE", s
 
    s.downcase! # <<< shouldn't work because it is frozen!
    assert_equal "hi name", s
  end
end

The issue is that the #freeze method went to the Recorder and not to the string. This is a problem you are likely to run into with a class like this because a standard ruby Object contains about 40 other methods; methods who’s names may conflict with what you want to delegate or capture with #method_missing (like the recorder). Thankfully Jim Weirich has created a BlankSlate class that you can use that will undefine all of the existing methods. If you have Recorder inherit from BlankSlate it will then work as expected. Neal mentioned that this class is being integrated into ruby 1.9 as SimpleObject.

When the slides become available checkout the section on the Quantifier module. The code is a bit lengthy to reproduce here but worth a look.

Share:
  • del.icio.us
  • Reddit
  • Technorati
  • Twitter
  • Facebook
  • Google Bookmarks
  • HackerNews
  • PDF
  • RSS
This entry was posted in rails, ruby and tagged . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.
  • http://www.rubyinside.com/railsconf-2008-round-up-910.html The Mega RailsConf 2008 Round Up

    [...] RailsConf08: Meta-programming Ruby for Fun and Profit by Nate Murray [...]

  • mike from africa

    hi, thx for the post~
    i just have some questions with the first example,

    ExistingKlass.class_eval do
    def change_a_method
    # …
    end
    end

    what happens when this code is being loaded before ExistingKlass has been defined? How does it “guarantee” that the class already exists? by loading it when it sees …class_eval?