Wednesday, July 12, 2006
Ruby Block Scope
While doing DSL work you constantly evaluate blocks (or code as strings) in various contexts. In fact, one key to using a DSL is the ability to evaluate in various contexts. Take this code for example:
However, instance_eval is only one of the ways a block can be executed. Blocks can also be executed by calling the call method:
Another way to execute a block is by using the yield statement. The yield statement also executes using the context in which the block was defined.
class SqlGeneratorThe above code allows you to generate a SQL statement by calling the SqlGenerator.evaluate method with a block:
class << self
def evaluate(&script)
self.new.instance_eval(&script)
end
end
def multiply(arg)
"select #{arg}"
end
def two(arg=nil)
"2#{arg}"
end
def times(arg)
" * #{arg}"
end
end
SqlGenerator.evaluate { multiply two times two }
=> "select 2 * 2"However, you could also execute the same code in the context of a calculator class to receive a result:class CalculatorWhich executes as:
class << self
def evaluate(&script)
self.new.instance_eval(&script)
end
end
def multiply(arg)
eval arg
end
def two(arg=nil)
"2#{arg}"
end
def times(arg)
" * #{arg}"
end
end
Calculator.evaluate { multiply two times two }
=> 4The (obvious) trick here is to use instance_eval to specify the scope in which the block executes. The instance_eval method evaluates either a string or block within the context of the receiver. The receivers in my examples were a new instance of SqlGenerator and a new instance of Calculator. When instance_eval is called on it's own, self is the receiver. Also, be sure to note that I call self.new.instance_eval. If I don't call the new method of self, the block will be evaluated by the class and not an instance of the class.However, instance_eval is only one of the ways a block can be executed. Blocks can also be executed by calling the call method:
def go(&block)In this case, the scope at creation time is used for evaluation.
block.call
end
go { 'called' }
=> "called"
class FooIn the above example, even though bar is defined in Foo the block was created in the context of Object (in irb) and bar is not defined in object. If you change the code to be:
def bar
"bar"
end
def do_something(&block)
block.call
end
end
Foo.new.do_something { bar }
NameError: undefined local variable or method `bar' for main:Object
from (irb):18
from (irb):15:in `do_something'
from (irb):18
from :0
def barWell, you see the results.
"this bar is called, but it's not the bar in Foo"
end
class Foo
def bar
"bar"
end
def do_something(&block)
block.call
end
end
Foo.new.do_something { bar }
=> "this bar is called, but it's not the bar defined in Foo"
Another way to execute a block is by using the yield statement. The yield statement also executes using the context in which the block was defined.
def barWhether to use yield, block.call, or instance_eval all depends on the context in which you need the code evaluated.
"this bar is called, but it's not the bar in Foo"
end
class Foo
def bar
"bar"
end
def do_something
yield
end
end
Foo.new.do_something { bar }
=> "this bar is called, but it's not the bar defined in Foo"
Comments:
<< Home
Thanks for this post, it just reinforced some stuff im doing to create a workflow dsm/rails plugin.
Any chance of getting some real world examples of how you are using dsm?
Any chance of getting some real world examples of how you are using dsm?
Unfortunately, all the real world examples I have are from clients; therefore, I cannot release the code.
Sorry.
Post a Comment
Sorry.
<< Home



