An example of how to do a Ruby DSL with nested blocks and no block params required

Posted: December 15, 2011 by Steve in Uncategorized
Tags: ,

Have you ever wondered how those DSL’s in ruby managed to get rid of the requirement to pass a variable to a block to indicate the context of the enclosed block? An example of this in Rails route definitions where the blocks are simply just nested. My question was how does each block know what it is nested inside of? After looking at actionpack I found the solution. It’s a little known (to me) function to execute an anonymous block in the context of an object: instance_exec. I have inserted it below with an example of its use. It is different than the one in activesupport, because instead of InstanceExecMethods.module_eval, I did self.class.instance_eval. The method below I got from apidock.com on the definition of instance_exec and exposing the source. The latest version of activesupport doesn’t seem to have it, so I don’t know where it resides now.

def instance_exec(*args, &block)
  begin
    old_critical, Thread.critical = Thread.critical, true
    n = 0
    n += 1 while respond_to?(method_name = "__instance_exec#{n}")
    InstanceExecMethods.module_eval { define_method(method_name, &block) }
  ensure
    Thread.critical = old_critical
  end
 
  begin
    send(method_name, *args)
  ensure
    InstanceExecMethods.module_eval { remove_method(method_name) } rescue nil
  end
end

My example just defines a tree of the Simpson’s. When a node is defined, its block is evaluated in the context of that new node object.

class Node
  def initialize(name=nil)
    @name = name
    @children = []
  end
 
  def node(name, &block)
    child = Node.new(name)
    @children.push(child)
    child.instance_exec(&block) if block
  end
end
 
def tree(name, &block)
  @tree = Node.new(name)
  @tree.instance_exec(&block)
  @tree
end
 
tree("Simpsons family tree") do
  node("gramps") do
    node("homer+marge") do
      node("bart")
      node("lisa")
      node("maggie")
    end
  end
end
 
puts "tree = #{tree.inspect}"

And the result is

tree = #<Node:0x1ee2433b @name="Simpsons family tree", @children=[#<Node:0x5c29ea31 @name="gramps", @children=[#<Node:0x578b1f8f @name="homer+marge", @children=[#<Node:0x408fbecf @name="bart", @children=[]>, #<Node:0x4cd4544 @name="lisa", @children=[]>, #<Node:0x153b2cb @name="maggie", @children=[]>]>]>]>

Comments are closed.