从Ruby中的方法调用中的块范围访问变量

时间:2016-08-29 01:23:46

标签: ruby-on-rails ruby lambda metaprogramming proc

我正在尝试创建一个与Rails assert_difference类似的最小的断言,但它会检查任何差异而不仅仅是数字差异。

这是我目前的实施:

Minitest::Assertions.module_eval do

  def assert_changed(expression, &block)
    unless expression.respond_to?(:call)
      expression = lambda{ eval(expression, block.binding) }
    end
    old = expression.call
    block.call
    refute_equal old, expression.call
  end

end

现在在我的测试套件中,我通过执行以下操作来调用它:

# this is working 
username = 'Jim'
assert_changed lambda{ username } do
  username = 'Bob'
end

# this is not working
username = 'Jim'
assert_changed 'username' do
  username = 'Bob'
end

使用lambda(或proc)对assert_changed的第一次调用完全正常。使用带有变量名称的字符串的第二次调用无效。

当它到达此行时:expression = lambda{ eval(expression, block.binding) } 我一直收到错误TypeError: no implicit conversion of Proc into String。有人可以向我解释如何让它发挥作用吗?

注意:我从Rails assert_difference方法获得了eval(expression, block.binding)想法。

1 个答案:

答案 0 :(得分:1)

  

当它到达此行时:as_strided我一直收到错误expression = lambda{ eval(expression, block.binding) }

我有理由相信你在点击那条线时得到那个错误,而是在击中这条线时:

TypeError: no implicit conversion of Proc into String

因此,异常不是由分配触发的,而是在old = expression.call Proccall编辑并且eval被执行后触发的。

此时,它会调用Proc中存储的expression。存储在Proc中的expression如下所示:

eval(expression, block.binding)

Kernel#eval的第一个参数必须是可隐式转换为String的内容(即响应to_str的内容),但它是Proc。你会得到TypeError

最简单的方法是重命名变量:

new_expression = expression
unless expression.respond_to?(:call)
  new_expression = lambda{ eval(expression, block.binding) }
end
old = new_expression.call
block.call
refute_equal old, new_expression.call

就个人而言,我可能更喜欢这样写:

module Minitest::Assertions
  def assert_changed(expression, &block)
    new_expression = if expression.respond_to?(:call)
      expression
    else
      -> { block.binding.eval(expression) }
    end

    old = new_expression.()
    block.()

    refute_equal old, new_expression.()
  end
end

没有(无用的)Module#module_eval,并使用call运算符语法和-> stabby lambda文字语法。