affectation de accesseur ajouté Dynamiquement ne fonctionne pas lors de l'appel via le bloc instance_eval en Ruby

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

Question

J'ai une classe à laquelle j'ajouter attribuer dynamiquement à l'exécution accesseurs. Cette classe fait partie d'un DSL, dans lequel les blocs sont transmis à des méthodes de configuration et appelées à l'aide instance_eval. Cela permet dans le DSL de supprimer les références à « soi » lors du référencement des méthodes de la classe.

Cependant, je l'ai découvert que je peux faire référence aux attributs pour récupérer leurs valeurs, mais je suis incapable de les attribuer, à moins que le référencement explicity soi-même, comme l'exemple de code suivant illustre.

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

Quelqu'un peut-il expliquer pourquoi cela ne se comporte pas comme prévu?

Était-ce utile?

La solution

La question arrises avec la façon dont Ruby traite par exemple et les variables locales. Ce qui se passe est que vous définissez une variable locale dans votre bloc instance_eval, plutôt que d'utiliser l'accesseur rubis.

Cela pourrait aider à expliquer:

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

La bocks façon dont le travail est qu'ils établissent une distinction entre les variables locales et d'instance, mais si une variable locale ne se définit pas quand il est lecteur est appelé, les paramètres par défaut de classe à la variable d'instance (comme cela est le cas avec l'appel à input_instance dans mon par exemple).

Il existe trois façons d'obtenir le comportement que vous voulez.

Utilisez des variables d'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"

Utilisez une variable auto:

    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"

Utiliser les fonctions 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"

Tous ces exemples mis foo.bar à "instance".

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top