3

I want to make a Test::Unit test_helper method that I can call to wipe 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?

Craig Walker
  • 49,871
  • 54
  • 152
  • 212

3 Answers3

4

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)
coreyward
  • 77,547
  • 20
  • 137
  • 166
  • ...which, unfortunately, I can't do, since the call to teardown isn't under my control. :-\ I was afraid of this, but thank you for the confirmation. – Craig Walker May 09 '11 at 15:36
4

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.

Guilherme Bernal
  • 8,183
  • 25
  • 43
1

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
Austin Taylor
  • 5,437
  • 1
  • 23
  • 29