Dynamisch hinzugefügt Accessor Zuordnung funktioniert nicht, wenn der Block über instance_eval in Ruby Aufruf

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

Frage

Ich habe eine Klasse, auf die ich Attribut Accessoren dynamisch zur Laufzeit hinzufügen. Diese Klasse bildet einen Teil einer DSL, wobei Blöcke Konfigurationsmethoden übergeben bekommen und mit instance_eval aufgerufen. Dies macht es im DSL möglich Verweise auf ‚Selbst‘ zu entfernen, wenn Referenzierung Methoden der Klasse.

Allerdings habe ich entdeckt, dass ich die Attribute verweisen können ihre Werte zu erhalten, aber ich bin nicht in der Lage, sie zu vergeben, es sei denn, explicity selbstbeziehende, wie das folgende Codebeispiel veranschaulicht.

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

Kann mir jemand erklären, warum dies nicht verhält wie erwartet?

War es hilfreich?

Lösung

Die Ausgabe arrises mit der Art und Weise, dass Ruby befasst sich mit Instanz und lokalen Variablen. Was geschieht, ist, dass Sie eine lokale Variable in Ihrem instance_eval Block setzen, anstatt den Rubin Zugriff verwendet wird.

Dies könnte helfen erklären:

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

Die Art und Weise bocks Arbeit ist, dass sie zwischen dem lokalen und Instanzvariablen unterscheiden, aber wenn eine lokale Variable nicht definiert ist, wenn es die Leser aufgerufen wird, die Klasse standardmäßig auf die Instanzvariable (wie es der Fall mit dem Aufruf von input_instance in meinem Beispiel).

Es gibt drei Möglichkeiten, um das gewünschte Verhalten zu erhalten.

Verwenden Instanzvariablen:

    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"

Verwenden Sie eine Selbst Variable:

    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"

Verwenden Sie set-Funktionen:

    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"

Alle diese Beispiele setzen foo.bar auf "Instanz".

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top