We are a software consultancy based in Berlin, Germany. We deliver
high quality web apps in short timespans.

Upstream Agile GmbH

Ruby Metaprogramming: Dynamically Inherited Methods™

September 19, 2009 by alex

Yesterday I added a new feature to Couch Potato, my persistence layer for CouchDB that I call Dynamically Inherited Methods™ and found the implementation I came up with interesting enough to share. Here’s the problem:

class User

  def self.string_attr_reader(name)
    define_method name do
      instance_variable_get("@#{name}").to_s
    end
  end

  string_attr_reader :nickname
  attr_writer :nickname
end

user = User.new
user.nickname = 123
user.nickname # => '123'

This code adds a macro to the class user called string_attr_reader. When you call this macro on the class and pass it a name it generates a method that returns the instance of the same name converted to a string. This all works perfectly until you want to override the generated method to add some behavior:

class User
  def nickname
    super.gsub('6', '7')
  end
end

The enhanced method now replaces all occurrences of 6 with a 7 - except it doesn’t. When you run the above code you will get an exception where Ruby complains there is no super to call. That’s because we defined the nickname method in the User class so we didn’t inherit it, hence no super.

The one way out of this is to use alias_method, or alias_method_chain when you are using Rails:

class User
  def nickname_with_six_replaced
    nickname_without_six_replaced.gsub('6', '7')
  end
  alias_method_chain :nickname, :six_replaced
end

First of all this isn’t very beautiful anymore, and secondly using alias_method_chain when you could use standard object oriented metaphors (like inheritance) doesn’t make a lot of sense - except that you can call yourself a metaprogramming programmer, yay.

Anyway, there is another way - actually involving much funkier meta programming - to solve the problem:

class User
  def self.string_attr_reader(name)
    accessor_module.module_eval do
      define_method name do
        instance_variable_get("@#{name}").to_s
      end
    end
  end

  private

  def self.accessor_module
    unless const_defined?('AccessorMethods')
      accessor_module = const_set('AccessorMethods', Module.new)
      include accessor_module
    end
    const_get('AccessorMethods')
  end
end

The updated code now dynamically creates a Module called User::AccessorMethods and includes it into the User class. All methods generated by string_attr_reader are now added to that new module instead of the class. The result is that when overriding those methods you can now call super because they’re are inherited from the new module.

class User
  attr_writer :nickname
  string_attr_reader :nickname

  def nickname
    super.gsub('6', '7')
  end
end

user = User.new
user.nickname = 678
user.nickname # => '778'

While this code involves some fairly crazy metaprogramming which would be too much for a simple example like the above, I think libraries like CouchPotato can still benefit, as the application code can become cleaner by not having to do any metaprogramming but resort to standard object oriented ways of programming.