Я пытаюсь сделать 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 заработал?
@Stefan: filter
является метод экземпляра. filters
, который устанавливает filter
, является методом класса. Однако filters
захватывает блок в контексте класса и передает его filter
; OP хочет, чтобы блок выполнялся в контексте экземпляра. (Пока не говорю, что это возможно или невозможно, мой мозг болит.)
@ Амадан Стефан прибил это в последнем комментарии, это точно моя проблема. Мне нужен метод класса для filters
, чтобы я мог создать DSL в посте (который является частью более крупного проекта), или, по крайней мере, я так думаю. Конечная цель здесь — настроить реализацию так, чтобы тест проходил для примера фильтра Foo
, который я создал.
@ Амадан, о, понятно, я думал, что filter
и filters
одно и то же. Я пропустил звонок define_method
.
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
или его двоюродных братьев нарушают ожидания программиста направо и налево — по замыслу вы разрушаете концепцию «области действия». Я почти уверен, что ОП знает об этом, но это стоит записать в пресловутую бумагу.
Кроме того, рассмотрите возможность использования средств доступа, а не простых переменных экземпляра — средства доступа проверяются, а переменные — нет. т. е. { |el| el == urg }
приведет к ошибке, но { |el| el == @urg }
завершится ошибкой (и отфильтрует nil
).
Вы правы насчет аксессуаров, я сделаю это. Спасибо!
@Stefan
@arg
класса, в который я звонюfilter
. Как мне тогда настроить реализацию, чтобы@arg
в блоке оценивался в рамках созданного класса?