2

我有一个在运行时动态添加属性访问器的类。此类构成 DSL 的一部分,其中块被传递给配置方法并使用 instance_eval 调用。这使得在 DSL 中可以在引用类的方法时删除对“self”的引用。

但是,我发现我可以引用属性来检索它们的值,但无法分配它们,除非明确引用 self,如下面的代码示例所示。

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

谁能解释为什么这不符合预期?

4

1 回答 1

4

问题出现在 Ruby 处理实例和局部变量的方式上。发生的事情是您在 instance_eval 块中设置了一个局部变量,而不是使用 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"]

块的工作方式是它们区分局部变量和实例变量,但是如果在调用读取器时未定义局部变量,则该类默认为实例变量(就像在我的示例中调用 input_instance 的情况一样)。

有三种方法可以得到你想要的行为。

使用实例变量:

    Foo 类
      attr_accessor :bar

      def 评估(&块)
        instance_eval &block
      结尾
    结尾

    foo = Foo.new
    foo.evaluate 做
      @bar = "实例"
    结尾
    foo.bar #"实例"

使用自变量:

    Foo 类
      attr_accessor :bar

      def 评估(&块)
        block.call(self)
      结尾
    结尾

    foo = Foo.new
    foo.evaluate 做 |c|
      c.bar = "实例"
    结尾
    foo.bar #"实例"

使用设置器函数:

    Foo 类
      attr_reader :酒吧
      def set_bar 值
        @bar = 值
      结尾

      def 评估(&块)
        instance_eval &block
      结尾
    结尾

    foo = Foo.new
    foo.evaluate 做
      set_bar "实例"
    结尾
    foo.bar #"实例"

所有这些示例都将 foo.bar 设置为“instance”。

于 2010-12-09T23:37:41.460 回答