Ruby 1.9 puts an extra hurdle in DSL development. Consider this simple DSL example which runs fine in 1.8:
class ProxyObject def initialize @o = Array.new end def method_missing(m, *a, &b) @o.send m, *a, &b end end def dsl(&b) ProxyObject.new.instance_eval &b end module A class B; end dsl do p Module.nesting # => [A] push B p pop # => A::B end end
This DSL simply proxies to an Array object. In ruby 1.8 everything will work, in specific, resolving the constant B, which resolves to the class A::B. This constant resolution works because we are in the lexical scope of class A, as we can see by the module nesting. (For more info on module nesting see this post)
Now run the code in ruby 1.9:
module A class B; end dsl do p Module.nesting # => [#<Class:#<ProxyObject:0x97beab8>>] push B # => uninitialized constant ProxyObject::B p pop end end
Now things break. Our module nesting no longer contains A because instance_eval has changed the nesting to the metaclass of our ProxyObject instance, which doesn’t help us at all. But wait… my last blog post to the rescue! In ruby 1.9 we can dynamically add modules to our lexical scope. So all we need to do is find the original module nesting of the block, then add those modules to our nesting, and then eval the block as we normally would:
def dsl(&b) modules = b.binding.eval "Module.nesting" Kernel.with_module(*modules) do ProxyObject.new.instance_eval &b end end module A class B; end dsl do p Module.nesting # => [#<Class:#<ProxyObject:0x8d6d190>>, #<Class:A>, A, Kernel] push B p pop # => A::B end end
Please let me know if you see any issues with this approach or have other ideas.
Credit goes to Lourens for mentioning this problem with 1.9 to me.