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 }However, you could also execute the same code in the context of a calculator class to receive a result:
=> "select 2 * 2"
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 }The (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.
=> 4
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"
Dude, another great post. Keep 'em coming. Great to see you at RailsConf.
ReplyDeleteThanks for this post, it just reinforced some stuff im doing to create a workflow dsm/rails plugin.
ReplyDeleteAny chance of getting some real world examples of how you are using dsm?
why did i say dsm when i really wanted to say DSL. *doh*
ReplyDeleteUnfortunately, all the real world examples I have are from clients; therefore, I cannot release the code.
ReplyDeleteSorry.