views:

75

answers:

2

I need a method that takes a block, and performs something similar to an around_each filter for every method within the block.

For instance:

def method_that_takes_block
  (@threads ||= Array.new) << Thread.new {yield if block.given?}
end

method_that_takes_a_block do
  method_one
  method_two
  method_three
end

In this instance I would like my method that takes a block to Thread each method within the block and pushes that thread to the @threads array. Essentially I'm just looking for a DRY way to wrap a thread around every method called within a block.

+3  A: 

You can't directly wrap a thread around each statement in the block body, if they can be arbitrary statements; there's no way to get that sort of control over the execution of a block body in Ruby. If you can restrict what goes in the block body, you have some more flexibility.

If each of the statements you are executing is simply a method call, as you imply in your example, you can use instance_exec to execute that block on a proxy object, which uses method_missing to spawn a new thread and then forward the method call on to the real object (or do whatever wrapper you're interested in; for the sake of example, I'll just wrap with some print statements):

class Proxy
  def initialize obj
    @obj = obj
  end
  def method_missing method, *args
    puts "<wrapper>"
    @obj.send method, *args
    puts "</wrapper>"
  end
end

class MethodWrapper
  def tell_me_a_joke
    puts "Knock, knock?"
  end
  def whos_there
    puts "Orange"
  end
  def orange_who
    puts "Orange you glad I didnt say banana?"
  end

  def wrap_around &blk
    Proxy.new(self).instance_exec &blk
  end
end

And here's how you can use it:

>> MethodWrapper.new.wrap_around { tell_me_a_joke; whos_there; orange_who }
<wrapper>
Knock, knock?
</wrapper>
<wrapper>
Orange
</wrapper>
<wrapper>
Orange you glad I didnt say banana?
</wrapper>
=> nil

The previous follows the pattern that you gave in your question, but it's less than ideal as only methods that are forwarded to the underlying object get wrapped:

>> MethodWrapper.new.wrap_around { tell_me_a_joke; puts "something" }
<wrapper>
Knock, knock?
</wrapper>
something
=> nil

You could instead just use instance_exec directly, and call the a wrapper method that takes a block, to get almost the same effect, though slightly less DRY as you need to call your wrapper method each time:

class SimpleWrapper
  def tell_me_a_joke
    puts "Knock, knock?"
  end
  def whos_there
    puts "Interrupting cow"
  end
  def interrupting_co
    puts "Moooooo!"
  end

  def wrap
    puts "<wrap>"
    yield
    puts "</wrap>"
  end
end

And in use:

>> SimpleWrapper.new.instance_exec do
     wrap { tell_me_a_joke }
     wrap { whos_there }
     wrap { interrupting_co }
     wrap { puts "Something" }
   end
<wrap>
Knock, knock?
</wrap>
<wrap>
Interrupting cow
</wrap>
<wrap>
Moooooo!
</wrap>
<wrap>
Something
</wrap>
=> nil
Brian Campbell
+1  A: 

You can also do it like this:

%w(method_one method_two method_three).each do |method|
    (@threads ||= Array.new) << Thread.new { self.send(method) }
end
kibitzer