使用define_method时块中的实例变量

时间:2019-05-28 10:24:10

标签: ruby metaprogramming

我正在尝试进行DSL,用户可以在其中传递一个块并期望定义实例变量@arg。这是一个单元测试失败的完整示例:

# Implementation
class Filter
  def initialize
    @arg = 'foo'
  end

  def self.filters &block
    define_method :filter do |els|
      els.select &block
    end
  end
end

# Usage
class Foo < Filter
  filters {|el| el == @arg}
end

# Expected behavior
describe 'filters created with the DSL' do
  subject { Foo.new }
  it 'can use @arg in the filters block' do
    els = %w[notthearg  either  foo  other]
    expect(subject.filter els).to be_eql(['foo'])
  end
end

使用pry或将puts语句放入块中,可以看到@arg为nil。但是Foo.new.instance_variable_get :@arg正确地输出了foo,因此它必须与某些作用域规则有关。

我需要在实现方式上进行哪些更改以使测试通过并使DSL正常工作?

1 个答案:

答案 0 :(得分:2)

instance_exec来营救!

class Filter
  def initialize
    @arg = 'foo'
  end

  def self.filters &block
    define_method :filter do |els|
      els.select { |e| self.instance_exec(e, &block) }
    end
  end
end

class Foo < Filter
  filters {|el| el == @arg }
end

Foo.new.filter(%w[notthearg  either  foo  other])
# => ["foo"]

警告:请确保对此文件进行充分记录,因为涉及instance_exec或其堂兄弟的所有恶名正左右颠倒了程序员的期望-在设计上,您正在破坏“范围”。我很确定OP知道这一点,但是值得把它放下这句谚语。

此外,考虑使用访问器而不是普通实例变量-检查访问器,而不检查变量。即{ |el| el == urg }会导致错误,但是{ |el| el == @urg }会自动失败(并过滤nil)。