переменная экземпляра в блоке при использовании define_method - PullRequest
1 голос
/ 28 мая 2019

Я пытаюсь создать 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 - ноль.Но Foo.new.instance_variable_get :@arg правильно выводит foo, так что это должно быть связано с некоторыми правилами области видимости.

Что мне нужно изменить в реализации, чтобы заставить проход теста и DSL работать?

1 Ответ

2 голосов
/ 28 мая 2019

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).

...