1

我正在尝试做一个 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 正常工作?

4

1 回答 1

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"]

Caution: Make sure this is very well documented, since any shenanigans involving instance_exec or its cousins are breaking programmer expectations left and right - by design, you're destroying the concept of "scope". I'm pretty sure OP knows this, but it is worth putting down on the proverbial paper.

Also, consider using accessors rather than plain instance variables - accessors are checked, and variables are not. i.e. { |el| el == urg } will result in an error, but { |el| el == @urg } will silently fail (and filter for nil).

于 2019-05-28T11:15:43.660 回答