tags:

views:

81

answers:

3

Anyone know how to get this to work if it's possible?

class Foo
  def self.go(&block)
    class << block
      include Bar
    end    
    puts "Within Foo#go: #{block.methods.include? 'baz'}"
    block.call
  end
end

module Bar
  def baz
    puts "I'm happily in my place!"
  end
end

Foo.go { 
  puts "Within actual block: #{methods.include? 'baz'}"
  baz
}

This gets the output:

Within Foo#go: true
Within actual block: false
NameError: undefined local variable or method ‘baz’ for main:Object

EDIT: when I print out the block's class in Foo#go, it's Proc, but when I print it out within the Proc, it's Object. Could this be related?

+1  A: 

You can't do this. The reason for what you're seeing is that there are two different contexts here. One is the context of the block, which closes over the context where it's defined. The other is the context of the Proc object wrapper, which is just the same as any other object context and completely unrelated to the context of the block itself.

I think the closest you'll get is to instance_eval the block using a context object that has the methods you want, but then the block won't have access to the self that existed where it was defined. It's up to you whether that makes sense for the method you want to write.

The other option is to pass the block an actual receiver for the baz method.

Chuck
Thanks, any chance you can explain exactly what happens to blocks, in terms of types, when they're passed to functions?
Eli
@Eli: I'm not sure what you mean. A block doesn't have a type in Ruby — it's just a language construct. The type of a Proc object is always Proc.
Chuck
Thanks, when I print out the class within a block, it's "Object", but then later it's "Proc".
Eli
@Eli: When you print `self.class` inside the block, you aren't printing the block's class — because, as I said, blocks aren't objects. `self` inside a block is the same `self` as in the context where the block is defined. It looks like you're creating the block at the top level of your script, so `self` at that time is an Object (a special Object called `main` serves as the context for the top level of a Ruby script). The Proc object passed to your method, on the other hand, is obviously a Proc.
Chuck
Ah, thanks a lot. Now it makes a lot more sense.
Eli
+2  A: 

You could use eval with Proc#binding:

module Bar
  def baz
    puts "hi from baz!"
  end
end
def go(&block)
  eval('include Bar', block.binding)
  block[]
end

baz #=> NameError
go { baz } #=> "hi from baz!"
baz #=> "hi from baz!"

But unless you use a mixin/mixout framework (like mixico or mixology), you'll be placing the methods from the included module into the lexical scope, so they'll still be accessible once the block returns.

require 'rubygems'
require 'mixico'

module Bar
  def baz
    puts "hi from baz!"
  end
end
def go(&block)
  Module.mix_eval(Bar, &block)
end

baz #=> NameError
go { baz } #=> "hi from baz!"
baz #=> NameError

Here's a good article on different ways to use a DSL from within a block.

rampion
Thanks, good article.
Eli
+1  A: 

Another alternative, following on from rampion, is to dup the context of the block before mixing into it, this way you're not messing up the context after you've finished.

module Bar
  def baz
    puts "hi from baz!"
  end
end
def go(&block)
  dup_context = eval('self', block.binding).dup
  dup_context.send(:include, Bar)
  dup_context.instance_eval &block
end

Note this will only be useful to you if you're not running any mutator methods in the block

banister