Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using yield inside define_method in Ruby

Is it possible to make yield keyword work inside a block given to define_method? Simple example:

class Test
  define_method :test do |&b|
    puts b    # => #<Proc:...>
    yield
  end
end

Test.new.test {
  puts "Hi!"
}

This code produces following error in both Ruby 1.8.7 and 1.9.0:

test.rb:4:in `test': no block given (LocalJumpError) from test.rb:8

The strange thing is the b block variable != nil but block_given? returns false. Is it intentional Ruby behaviour not to recognize blocks by Proc objects?

Edit: Regards to Beerlington's answer: b.call() is is not what I am looking for. Block variable was used only to indicate that block is actually given, and is not detected inside define_method.

Reason why I need use yield instead of block.call

I am willing to write some extension to the way how new classes are defined in Ruby, thus any code You can write in pure Ruby should be accepted when I use my extension.

So similar semantics cannot be taken into consideration, because this forces users of my library to use only one proper way to pass a block. This breaks the TIMTOWTDI rule, and does not make my library transparent.

Real life example

Code below can be simplified to code above since my_def uses define_method:

require 'my_library'

class Test
  # client can write 'my_def' instead of 'def' since
  # my_library extends Class class
  my_def :test, "some parameter" do
    yield        # oh no, error :(
  end
end

Test.new.test {
  puts "Hi!"
}
like image 420
Dawid Avatar asked Feb 21 '10 17:02

Dawid


2 Answers

I think this is what you're looking for:

class Test
  define_method :test do |&b|
    b.call
  end
end

Test.new.test {
  puts "Hi!"
}

More at http://coderrr.wordpress.com/2008/10/29/using-define_method-with-blocks-in-ruby-18/

like image 88
Peter Brown Avatar answered Oct 15 '22 09:10

Peter Brown


You cannot use yield inside a define_method block. This is because blocks are captured by closures, observe:

def hello
  define_singleton_method(:bye) { yield }
end

hello { puts "hello!" }

bye {  puts "bye!" } #=> "hello!"

I don't think your users will mind not being able to use 'yield' in the way you state ---- the syntax is nothing like ordinary Ruby method definition syntax so there is unlikely to be any confusion.

More information on why you cannot pass blocks implicitly to methods found here: http://banisterfiend.wordpress.com/2010/11/06/behavior-of-yield-in-define_method/

like image 24
horseyguy Avatar answered Oct 15 '22 09:10

horseyguy