Basically the interface designer should have chosen to do so due to this little trick regarding scopes and how eval
and instance_eval
work, check this example:
Having 2 classes Foo
and Boo
with the following definitions:
class Foo
def speak(phrase)
puts phrase
end
def self.generate(&block)
f = Foo.new
f.instance_eval(&block)
end
end
class Boo
attr_reader :name
def initialize(name) ; @name = name ; end
def express
Foo.generate { speak name}
end
end
Generally this should work fine for most cases, but some situations like the following statement will issue an error:
Boo.new("someone").express #`express': undefined local variable or method `name' for #<Foo:0xb7f582fc> (NameError)
We don't have access here to the instance methods of Boo
inside Foo
instances that's because we are using instance_eval
, so the method name
which is defined for Boo
instances is not in the scope for Foo
instances.
To overcome such problems it’s better to redefine generate as follows:
class Foo
def speak(phrase)
puts phrase
end
def self.generate(&block)
f = Foo.new
block.arity < 1 ? f.instance_eval(&block) : block.call(f)
end
end
This is a flexible interface where you evaluate the code block depending on the passed block params. Now we have to pass the current foo object as a param when we need to call instance methods on it, let's redefine Boo
, check express
and talk
:
class Boo
attr_reader :name
def initialize(name) ; @name = name ; end
def express
Foo.generate { |f| f.speak name}
end
def talk(anything)
Foo.generate { speak anything}
end
end
Boo.new("someone").express #=> someone
Boo.new("someone").talk("whatever") #=> whatever