Eli
Eli

Reputation: 1269

Including methods into blocks

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?

Upvotes: 3

Views: 1712

Answers (3)

rampion
rampion

Reputation: 89043

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.

Upvotes: 3

horseyguy
horseyguy

Reputation: 29895

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

Upvotes: 3

Chuck
Chuck

Reputation: 237010

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.

Upvotes: 4

Related Questions