Динамически добавленная присвоение аксессуаров не работает при вызове блока через экземпляр_eval в Ruby

StackOverflow https://stackoverflow.com/questions/4403973

Вопрос

У меня есть класс, к которому я динамически добавляю атрибуты динамически во время выполнения. Этот класс формирует часть DSL, в результате чего блоки передаются в методы настройки и вызывают с использованием экземпляра_eval. Это позволяет в DSL для удаления ссылок на «я» при ссылке на методы класса.

Тем не менее, я обнаружил, что я могу ссылаться на атрибуты для получения их значений, но не могу присвоить их, если только ссылка ссылается, что иллюстрирует следующий образец кода.

class Bar

  def add_dynamic_attribute_to_class(name)
    Bar.add_dynamic_attribute(name)
  end

  def invoke_block(&block)
    instance_eval &block
  end

  def self.add_dynamic_attribute(name)
    attr_accessor name
  end

end

b = Bar.new

b.add_dynamic_attribute_to_class 'dyn_attr'

b.dyn_attr = 'Hello World!'

# dyn_attr behaves like a local variable in this case
b.invoke_block do
  dyn_attr = 'Goodbye!'
end

# unchanged!
puts "#{b.dyn_attr} but should be 'Goodbye!'"

# works if explicitly reference self
b.invoke_block do
  self.dyn_attr = 'Goodbye!'
end

# changed...
puts "#{b.dyn_attr} = 'Goodbye!"

# using send works
b.invoke_block do
  send 'dyn_attr=', 'Hello Again'
end

# changed...
puts "#{b.dyn_attr} = 'Hello Again!"

# explain this... local variable or instance method?
b.invoke_block do

  puts "Retrieving... '#{dyn_attr}'"

  # doesn't fail... but no effect
  dyn_attr = 'Cheers'

end

# unchanged
puts "#{b.dyn_attr} should be 'Cheers'"

Кто-нибудь может объяснить, почему это не ведет себя как ожидалось?

Это было полезно?

Решение

Проблема напрашится с тем, как Ruby предлагает сделки экземплярами и локальными переменными. Что происходит, вы устанавливаете локальную переменную в вашем блоке экземпляра_eval, а не с использованием Accessor Ruby.

Это может помочь объяснить это:

class Foo
  attr_accessor :bar

  def input_local
    bar = "local"
    [bar, self.bar, @bar, bar()]
  end

  def input_instance
    self.bar = "instance"
    [bar, self.bar, @bar, bar()]
  end

  def input_both
    bar = "local"
    self.bar = "instance"
    [bar, self.bar, @bar, bar()]
  end
end

foo = Foo.new
foo.input_local #["local", nil, nil, nil]
foo.input_instance #["instance", "instance", "instance", "instance"]
foo.input_both #["local", "instance", "instance", "instance"]

Way Bocks Work - это то, что они различают локальные переменные и экземпляра, но если локальная переменная не определена, когда его читатель называется, класс по умолчанию для переменной экземпляра (как это имеет значение с вызовом на input_instance в моем примере).

Есть три способа получить поведение, которое вы хотите.

Использовать переменные экземпляра:

 Class Foo ittr_accessor: BAR DEF Оценить (& Block) Exmance_eval & Block End End Foo = foo.new foo.evalatue do @bar = "экземпляр" ENCE FO.BAR # "Экземпляр"

Используйте самомую переменную:

 Class Foottr_accessor: BAR DEF Оценить (& Block) Block.Call (Self) End End Foo = foo.new foo.evalatue do | c | C.BAR = «Экземпляр» End Foo.bar # "Экземпляр"

Используйте функции сеттера:

 Class Foo ittr_Reader: BAR DEF SET_BAR Значение @bar = incep end dep Оценить (& block) instance_eval & block End End foo = foo.new foo.evalute do set_bar "экземпляр" Enterption foo.bar # "экземпляр"

Все эти примеры устанавливают Foo.bar на «экземпляр».

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top