coderrr

June 2, 2009

Fixing constant lookup in DSLs in Ruby 1.9.1

Filed under: ruby, ruby19 — Tags: , — coderrr @ 8:15 pm

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.

4 Comments »

  1. I guess the funniest part is this coming up just a few days after you posted the with_const post :)

    Comment by mtoledo — June 3, 2009 @ 12:52 am

  2. Thanks for this post. I had just posted to ruby-talk because I noticed the same thing.

    Comment by gilgamesh — June 27, 2009 @ 6:30 am

  3. What you describe is true for Ruby 1.9.1, but in Ruby 1.9.2 your code behaves the same as it does in Ruby 1.8.

    Comment by Matt Robinson — March 23, 2011 @ 7:31 pm

  4. Yea, I wrote this over a year before 1.9.2 came out, I’ll fix the title.

    Comment by coderrr — March 24, 2011 @ 8:17 am


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Silver is the New Black Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 28 other followers

%d bloggers like this: