通过instance_eval的Ruby中调用块时动态添加访问分配不工作
-
25-09-2019 - |
题
我有一个类而我在运行时动态添加属性访问器。此类形式的一部分DSL,由此得到的块传递给配置的方法和使用instance_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'"
任何人都可以解释为什么预期,这是不正常?
解决方案
问题棱与红宝石的实例和局部变量交易的方式。正在发生的事情是,你要设置一个局部变量在instance_eval的块,而不是使用红宝石访问。
这可能有助于解释它:
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"]
在方式bocks工作是他们区分本地和实例变量之间,但如果没有定义的局部变量,当它的阅读器被调用时,该类默认为实例变量(如与在调用input_instance的情况下我例子)。
有三种方式得到你想要的行为。
使用实例变量:
class Foo attr_accessor :bar def evaluate(&block) instance_eval &block end end foo = Foo.new foo.evaluate do @bar = "instance" end foo.bar #"instance"
使用的自变量:
class Foo attr_accessor :bar def evaluate(&block) block.call(self) end end foo = Foo.new foo.evaluate do |c| c.bar = "instance" end foo.bar #"instance"
使用setter函数:
class Foo attr_reader :bar def set_bar value @bar = value end def evaluate(&block) instance_eval &block end end foo = Foo.new foo.evaluate do set_bar "instance" end foo.bar #"instance"
所有这些例子设置foo.bar到 “实例”。
不隶属于 StackOverflow