asignación de acceso añadido dinámicamente no funciona cuando se invoca el bloque a través de instance_eval en Ruby

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

Pregunta

Tengo una clase a la que añado descriptores de acceso de atributos dinámicamente en tiempo de ejecución. Esto forma parte de la clase una DSL, en el que los bloques se pasan a los métodos de configuración y invocan utilizando instance_eval. Esto hace que sea posible en el DSL para eliminar las referencias a 'yo' al hacer referencia a los métodos de la clase.

Sin embargo, he descubierto que puedo hacer referencia a los atributos para recuperar sus valores, pero soy incapaz de asignarlos, a menos que explícitamente referencia a sí mismo, como ilustra el siguiente ejemplo de código.

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

Puede alguien explicar por qué esto no se comporta como se esperaba?

¿Fue útil?

Solución

La cuestión aristas con la forma en que Ruby se ocupa de instancia y variables locales. Lo que está sucediendo es que se está configurando una variable local en su bloque instance_eval, en lugar de utilizar el descriptor de acceso rubí.

Esta ayuda podría explicar que:

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 forma de trabajar de bock es que se distinguen entre variables locales y de instancia, pero si no se define una variable local cuando de lector se llama, los valores por defecto de la clase a la variable de instancia (como es el caso de la llamada a input_instance en mi ejemplo).

Hay tres maneras de obtener el comportamiento que desea.

Use variables de instancia:

    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"

Usar una 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"

Uso de las funciones 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"

Todos estos ejemplos establecidos foo.bar a "ejemplo".

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top