coderrr

October 29, 2008

Using define_method with blocks in Ruby 1.8

Filed under: ruby — Tags: — coderrr @ 10:53 pm

Shameless Plug: Unblock websites with a VPN Tunnel.

It’s pretty well known that because of a deficiency in blocks arguments in Ruby 1.8 Class#define_method cannot define methods that take blocks.

def x *args, █ end  # => works!
define_method(:x) {|*args,&block| } # => SyntaxError: compile error

This really sucks when you need to define methods dynamically which take blocks. Sometimes you have to define the method dynamically because it needs access to variables in the current scope. If you were to use the def syntax the scope changes and those variables would no longer be accessible. For example:

var1 = x
var2 = y
def my_method &block
  block.call(var1*var2) # => undefined local variable or method `var1'
end

I came up with a simple workaround for this

  var1 = x
  var2 = y
  define_method(:__real__my_method) do |block, *args|
    block.call(var1*var2)
  end

  def my_method(*args, &block)
    __real__my_method(block, *args)
  end

We create a special “real” method which takes the block as the normal first parameter instead of as the special block parameter. Then we create a proxy method, with the name that we want to use in our code, which simply re-arranges the parameters (putting the block first) and calls the “real” method.

There’s one other possible issue. Since the method we’ll be calling from our code is still being defined with a def you might be complaining that you can’t set its name dynamically. Well there are two ways to do this. One is to just eval a string:

  eval <<-EOM
    def #{dynamic_name}(*args, &block)
      __real__#{dynamic_name}(block, *args)
    end
  EOM

Sometimes though, you wan’t to use a name which has characters that the ruby parser won’t allow you to use with the def syntax. This can be accomplished with a temporary method name and a alias_method:

  eval <<-EOM
    def __temp_method__(*args, &block)
      __send__("__real__#{dynamic_name}", block, *args)
    end
  EOM
  alias_method dynamic_name, :__temp_method__
  remove_method :__temp_method__

7 Comments »

  1. # define_method(:x) {|*args,&block| }
    # => SyntaxError: compile error

    No longer an issue in Ruby 1.9.

    Comment by Hongli Lai — October 30, 2008 @ 1:38 am

  2. As Hongli Lai said, this is coming in Ruby 1.9. Also, if you’re going the route of using mutli-line strings, why have the second method at all? Doesn’t the following work just fine?

    Comment by Drew Olson — October 30, 2008 @ 3:56 am

  3. Hrm, gist embedding doesn’t work in comments :) Here’s the link:

    http://gist.github.com/20910

    Comment by Drew Olson — October 30, 2008 @ 3:57 am

  4. Yes I’m quite aware that this has been in 1.9 since the beginning. Which is why I specified Ruby 1.8 in the title of the post :)

    @Drew

    All your method does is basically:

    def name(*a); yield *a; end

    but replacing name with whatever you specify.

    The point of the post is to show you how to define methods dynamically with a block. The reason you might want to use a block is to capture the variables in the current scope which you can’t do with def or with an eval.

    Or maybe I misunderstood your question, care to clarify?

    Comment by coderrr — October 30, 2008 @ 4:07 am

  5. @Chris ah, makes sense, i skimmed too quickly. the ability to define a method with a block (as a closure) that take a block makes sense.

    Thanks for the clarification.

    Comment by Drew — October 30, 2008 @ 4:47 am

  6. For the record: define_method(:x) {|*args, &block| } work just fine in 1.8.7.

    Comment by AlekSi — November 17, 2010 @ 7:59 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

Customized 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: