Defining a method that uses an out-of-scope variable in Ruby
I want to make a Test::Unit test_helper method that I can call to wip开发者_如何学JAVAe a bunch of tables after the tests execute. Here's the general idea I have:
def self.wipe_models(*models)
def teardown
models.each do |model|
model = model.to_s.camelize.constantize
model.connection.execute "delete from #{model.table_name}"
end
end
end
However, when teardown
runs, I get:
undefined local variable or method `models'
To me it looks like the "def" block doesn't obey usual rules for closures; I can't access variables defined outside of its scope.
So, how do I access a variable that's defined outside of a "def" method declaration?
You can do it as a closure with define_method
:
def self.wipe_models(*models)
define_method(:teardown) do
models.each do |model|
model = model.to_s.camelize.constantize
model.connection.execute "delete from #{model.table_name}"
end
end
end
Now the method body is a block and can access models
.
Method definitions are not closures in Ruby. The class
, module
, def
, and end
keywords are all scope gates. In order to maintain scope across a scope gate you have to pass a block; blocks are closures and thus run in the scope in which they were defined.
def foo
# since we're in a different scope than the one the block is defined in,
# setting x here will not affect the result of the yield
x = 900
puts yield #=> outputs "16"
end
# x and the block passed to Proc.new have the same scope
x = 4
square_x = Proc.new { x * x }
foo(&square_x)
Use a class instance variable:
cattr_accessor :models_to_wipe
def self.wipe_models(*models)
self.models_to_wipe = models
end
def teardown
self.class.models_to_wipe.each do |model|
model = model.to_s.camelize.constantize
model.connection.execute "delete from #{model.table_name}"
end
end
精彩评论