Search code examples
rubysingletonmetaprogramming

define_singleton_method with a block argument (or, how to give two blocks as arguments)


I'm trying to run this code, to overwrite each for an Array instance:

my_array.define_singleton_method(:each, &block) do
  super { |x| x.instance_exec(&block) }
end

The problem is I get this error both block arg and actual block given (SyntaxError), because I'm giving two block arguments to define_singleton_method

How can I redefine each while using each's block, if I need a block for the method definition?


Solution

  • Your your issue is the passing of &block as an explicit argument to define_singleton_method and also passing an implicit block.

    What you actually want to do is capture &block as an argument to your new each method so that you can pass that block forward.

    Essentially define_singleton_method looks like this:

    define_singleton_method(method_name) {|method_arguments| } 
    

    So you can accomplish your goal as follows:

    my_array = [1,2,3]
    my_array.define_singleton_method(:each) do |&block|
      puts 'new each' 
      super(&block)
    end 
    
    my_array.each {|i| puts i * 2} 
    # new each
    # 2
    # 4
    # 6 
    #=> [1,2,3]
    

    This is essentially the same as

    def my_array.each(&block) 
      puts 'new each' 
      super
    end 
    # or even 
    def my_array.each
      puts 'new each' 
      super
    end 
    

    The only difference is def...end method declaration allows for implicit argument passing, as shown, whereas define_singleton_method does not, so we must capture the block and explicitly pass it to super.