Meta-programming with instance_exec

Posted by Paul Ingles
Sunday, November 04, 2007 11:57:00 GMT

Rails makes heavy use of a declarative style around it’s codebase- for example the has_one and belongs_to declarations inside ActiveRecord (amongst others). These are just class methods defined on modules, letting Rails wire up relationships, but they read like fully-fledged statements within a mini-language:

class Student < ActiveRecord::Base
  has_many :tutorials

We can take advantage of the same in our code- letting us write in a declarative-style (hopefully revealing stronger intent) and reduce the amount of code we write (by using declarations to meta-program for us). I posted a little while ago that we’d used such an approach for marking attributes as immutable.

Anyway, back to the title of the post- #instance_exec, it’s a method defined in Ruby Facets. Like it’s documentation says, it’s equivalent to instance_eval but also lets you pass parameters- roll on the meta magic.

Let’s say we’re writing a system to calculate the monthly salary payment for an employee. We want to be able to say what the payment is rather than how it’s calculated.

class FullTimeEmployee
  include Employee

  bill_at {|hours| 50.pounds_sterling * hours}

In the snippet above, we’re making a declaration - defining the relationship between an hourly rate and the number of hours the employee works. We can implement bill_at in Employee as follows:

module Employee
  module ClassMethods
    def bill_at &block
      define_method(:calculate_bill) do |hours|
        instance_exec hours, &block
      end
  ...

Our assertion could be:

assert_equal 500.pounds_sterling, employee.calculate_bill(5.hours)
Comments

Leave a response