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=[]>]>]>]> |