Ruby's Metaprogramming Toolbox

What is Metaprogramming?

Metaprogramming is the writing of computer programs that write or manipulate other programs (or themselves) as their data, or that do part of the work at compile time that would otherwise be done at runtime. In many cases, this allows programmers to get more done in the same amount of time as they would take to write all the code manually, or it gives programs greater flexibility to efficiently handle new situations without recompilation. (via Wikipedia)

The following tutorial lists all the methods from the Ruby core that are useful for metaprogramming as well as demonstrates common usage scenarios you will find helpful to get started. In conclution, an example is presented showing how to develop a dynamic database class like ActiveRecord which automatically generates classes for database tables and populates each model class with getters and setters for its fields.

The Metaprogramming Tool Box

Ruby gives us many methods that help us generate code dynamically. Its important to familiarize yourself with them.

Getting, setting, and destroying variables

Getting, setting, and destroying constants (like classes)

Adding and removing methods

Running dynamic code

Reflection methods

Reflection is an important part of metaprogramming as it allows us to look at objects and peer at their contents and structure.

Evaluating strings and blocks

You may be familiar with the eval method which allows you to evaluate a string or block as ruby code. When you need to eval within the scope of a particular object you can use the instance_eval and module_eval (synonymous for class_eval) methods.

The instance_eval method works on the scope of an instantiated object.

[1,2,3,4].instance_eval('size') # returns 4

In the above example we passed instance_eval the string ‘size’ which is interpreted against the receiving object. It is equivalent to writing:

[1,2,3,4].size

You can also pass instance_eval a block.

# Get the average of an array of integers
[1,2,3,4].instance_eval { inject(:+) / size.to_f } # returns 2.5

Notice how the inject(:+) and size.to_f methods just float in the air with no receiving objects? Well because they are executed within the instance context they are evaluated as self.inject(:+) / self.size.to_f where self is the receiving object array.

Whereas instance_eval evaluates code against an instantiated object module_eval evals code against a Module or Class.

Fixnum.module_eval do
  def to_word
    if (0..3).include? self
      ['none', 'one', 'a couple', 'a few'][self]
    elsif self > 3
      'many'
    elsif self < 0
      'negative'
    end
  end
end
1.to_word # returns 'one'
2.to_word # returns 'a couple'

We can see how module_eval re-opened the existing class Fixnum and appended a new method. Now this in itself is nothing special as there are other ways to do this for instance:

class Fixnum
  def to_word
    ..
  end
end

The real advantage is to evaluate strings that generate dynamic code. Here we are using a class method create_multiplier to dynamically generate a method with a name of your choosing.

class Fixnum
  def self.create_multiplier(name, num)
    module_eval "def #{name}; self * #{num}; end"
  end
end

Fixnum.create_multiplier('multiply_by_pi', Math::PI)
4.multiply_by_pi # returns 12.5663706143592

The above example creates a class method (or ‘singleton method’) which when called, generates instance methods which any Fixnum object can use.

Using send

Using send works much like instance_eval in that it sends a method name to a receiving object. It is useful when you are dynamically getting a method name to call from a string or symbol.

method_name = 'size'
[1,2,3,4].send(method_name) # returns 4

You can specify the method name as a string or a symbol ‘size’ or :size

One potential benefit of send is that it bypasses method access control and can be used to run private methods like Module#define_method.

Array.define_method(:ducky) { puts 'ducky' }  
# NoMethodError: private method `define_method' called for Array:Class

Using the send hack:

Array.send(:define_method, :ducky) { puts 'ducky' }

Defining Methods

As we just saw in the example above we can use define_method to add methods to classes.

class Array
  define_method(:multiply) do |arg|
    collect{|i| i * arg}
  end
end

[1,2,3,4].multiply(16) # returns [16, 32, 48, 64]

method_missing

When included in a class, method_missing is invoked when the class instance receives a method that does not exist. It can be used to catch these missing methods instead of raising a NoMethodError.

class Fixnum
  def method_missing(meth)
    method_name = meth.id2name
    if method_name =~ /^multiply_by_(\d+)$/
      self * $1.to_i
    else
      raise NoMethodError, "undefined method `#{method_name}' for #{self}:#{self.class}"
    end
  end
end

16.multiply_by_64 # returns 1024
16.multiply_by_x # NoMethodError

How does attr_accessor work?

Most of us use attr_accessor in our classes, but not everyone understands what it does behind the scenes. attr_accessor dynamically generates a getter and a setter for an instance variable. Lets take a closer look.

class Person
  attr_accessor :first_name
end

john = Person.new
john.first_name = 'John'

john.instance_variables 
# returns ["@first_name"]

john.methods.grep /first_name/ 
# returns ["first_name", "first_name="]

We can see that attr_accessor actually created an instance variable @first_name as well as two instance methods, a getter and a setter, first_name and first_name=

Implementation

All classes inherit class methods from Module so we will put the mock methods here.

class Module
  # First using define_method
  def attr1(symbol)
    instance_var = ('@' + symbol.to_s)
    define_method(symbol) { instance_variable_get(instance_var) }
    define_method(symbol.to_s + "=") { |val| instance_variable_set(instance_var, val) }
  end

  # Second using module_eval
  def attr2(symbol)
    module_eval "def #{symbol}; @#{symbol}; end"
    module_eval "def #{symbol}=(val); @#{symbol} = val; end"
  end
end

class Person
  attr1 :name
  attr2 :phone
end

person = Person.new
person.name = 'John Smith'
person.phone = '555-2344'
person # returns <Person:0x28744 @name="John Smith", @phone="555-2344">

Both define_method and module_eval produced the same result.

Example Usage: Poor Man’s Active Record

For those familiar with RubyonRails it is easy to see how one might go about implementing an ActiveRecord class which would look up field names in a database and add getters and setters to a class.

We could take it one step further and have the Model classes generated dynamically as well.

In this example we are going to generate a poor man’s ActiveRecord. The class will connect to the MySQL database, generate a dynamic class for every table it finds, and populate the classes with getters and setters the table fields they contain.

require 'rubygems'
require 'mysql'

class PoorMan
  # store list of generated classes in a class instance variable
  class << self; attr_reader :generated_classes; end
  @generated_classes = []

  def initialize(attributes = nil)
    if attributes
      attributes.each_pair do |key, value|
        instance_variable_set('@'+key, value)
      end
    end
  end

  def self.connect(host, user, password, database)
    @@db = Mysql.new(host, user, password, database)

    # go through the list of database tables and create classes for them
    @@db.list_tables.each do |table_name|
      class_name = table_name.split('_').collect { |word| word.capitalize }.join

      # create new class for table with Module#const_set
      @generated_classes << klass = Object.const_set(class_name, Class.new(PoorMan))

      klass.module_eval do
        @@fields = []
        @@table_name = table_name
        def fields; @@fields; end
      end

      # go through the list of table fields and create getters and setters for them
      @@db.list_fields(table_name).fetch_fields.each do |field|
        # add getters and setters
        klass.send :attr_accessor, field.name

        # add field name to list
        klass.module_eval { @@fields << field.name }
      end
    end
  end

  # finds row by id
  def self.find(id)
    result = @@db.query("select * from #{@@table_name} where id = #{id} limit 1")
    attributes = result.fetch_hash
    new(attributes) if attributes
  end

  # finds all rows
  def self.all
    result = @@db.query("select * from #{@@table_name}")
    found = []
    while(attributes = result.fetch_hash) do
      found << new(attributes)
    end
    found
  end
end

# connect PoorMan to your database, it will do the rest of the work for you
PoorMan::connect('host', 'user', 'password', 'database')

# print a list generated classes
p PoorMan::generated_classes

# find user with id:1
user = Users.find(1)

# find all users
Users.all


About this entry


Comments

  1. Avatar

    geo

    Posted: 1 day later:

    Useful stuff for anyone looking to write dynamic code. Also very useful for customizing modules through monkey patching.

  2. Avatar

    sanjayts

    Posted: 4 days later:

    Any specific reason you refer to Class methods as ‘Singleton’ methods and not ‘Static’ methods?

  3. Avatar

    mike in africa

    Posted: 4 days later:

    sanjayts, .Net guys talk about ‘static’ methods. You never hear ‘static’ methods in Ruby.

    I think it’s ‘singleton’ because everything in Ruby is an object including classes, and thus it would even be incorrect to call them class methods, but singleton methods defined on these instances of Class.

  4. Avatar

    Corban

    Posted: 4 days later:

    @sanjayts, mike is correct, notice how a new class ..

    class Klass < Parent
    end

    ..is really just an instance of Class. This is equivalent:

    klass = Class.new(Parent)
    Object.const_set('Klass', klass)

    whereas Parent is some already declared class to inherit from.

    A class is an object like any other and can have its own single-ton class added to it.

    In ruby it is common to refer to a method which operates on a single object as a singleton.

    I often write custom process methods for a specific data sets. Not every object needs this method. Just the dataset in question.

    For instance lets say we had a config Hash

    config = {:host => 'localhost', :user => 'user', :pass => 'pass'}

    I could create add a validation singleton method to this object

    def config.is_valid?
      # test to see options are ok.
    end

    In the same vein it makes sense to refer to class instance variables as singleton variables

    class Klass
      @test = 'class instance var'
    
      def initialize
        @test = 'instance var'
      end
    end
    
    Klass.instance_variable_get :@test # class instance var
    Klass.new.instance_variable_get :@test # instance var
  5. Avatar

    Chris

    Posted: 15 days later:

    this was a really great explanation of ruby metaprogramming. nothing i’ve read so far broke it down into such an easy-to-understand guide.

  6. Avatar

    Jerod Santo

    Posted: 16 days later:

    Really nice article, guys. I “get” metaprogramming in Ruby but never seem to find great uses for it.

    It’s nice to see your implementation of an ActiveRecord-esque class to show a practical use of metaprogramming. Thanks!

About

    Buildingsky.net is comprised of Corban Brook and Maciek Adwent. We build experimental web applications.

    We are interested in computer science, ruby-lang, javascript, web technologies, audio synthesis, finance/economics.

Contact

Projects

Categories