Accediendo a las variables de clase Ruby con class_eval y instance_eval
instance-eval (4)
Acabo de hacerle la misma pregunta a Matz durante la fiesta de RubyKaigi. Estaba medio borracho, pero él estaba perfectamente sobrio, así que puedes tomar esto como la respuesta definitiva.
Anton tiene razón: la razón por la que no puede acceder a las variables de clase a través de instance_eval () es "solo porque". Incluso class_eval () comparte el mismo problema (el propio Matz no estaba totalmente seguro acerca de class_eval () hasta que le dije que ya lo había intentado). Más específicamente: el alcance, las variables de clase son más constantes que las variables de instancia, por lo que cambiar self (como instance_eval () y class_eval () do) no hará ninguna diferencia cuando se trata de acceder a ellas.
En general, podría ser una buena idea evitar por completo las variables de clase.
Tengo los siguientes
class Test
@@a = 10
def show_a()
puts "a: #{@@a}"
end
class << self
@@b = ''40''
def show_b
puts "b: #{@@b}"
end
end
end
¿Por qué funciona el siguiente trabajo?
Test.instance_eval{show_b}
b: 40
=> nil
¿Pero no puedo acceder a @@b
directamente?
Test.instance_eval{ @@b }
NameError: uninitialized class variable @@b in Object
Asimismo, las siguientes obras.
t = Test.new
t.instance_eval{show_a}
a: 10
=> nil
pero falla lo siguiente
t.instance_eval{ @@a }
NameError: uninitialized class variable @@a in Object
No entiendo por qué no puedo acceder a las Variables de clase directamente desde los bloques de instance_eval
.
Bueno, probablemente la mejor respuesta es "solo porque": el instance_eval en pocas palabras crea algún tipo de proceso singleton que se invoca con el enlace de un objeto dado. Estoy de acuerdo en que suena un poco extraño, pero es lo que es.
Si ejecuta instance_eval con una cadena, incluso recibirá una advertencia de que su método intenta acceder a la variable de clase:
irb(main):038:0> Test.new.instance_eval "@@a"
(eval):1: warning: class variable access from toplevel singleton method
NameError: (eval):1:in `irb_binding'': uninitialized class variable ...
EDITAR: el código siguiente se probó con 1.8.7 y 1.9.1 ... parece que la situación es diferente nuevamente con 1.9.2: /
La situación en realidad no es tan sencilla. Hay diferencias en el comportamiento dependiendo de si está usando 1.8 o 1.9 y si está usando class_eval
o instance_eval
.
Los ejemplos a continuación detallan el comportamiento en la mayoría de las situaciones.
También incluí el comportamiento de las constantes, en buena medida, ya que su comportamiento es similar, pero no exactamente igual, a las variables de clase.
Variables de clase
class_eval
en Ruby 1.8:
class Hello
@@foo = :foo
end
Hello.class_eval { @@foo } #=> uninitialized class variable
class_eval
en Ruby 1.9:
Hello.class_eval { @@foo } #=> :foo
Así que las variables de clase se class_eval
en 1.9 (pero no en 1.8) cuando se usa class_eval
instance_eval
en Ruby 1.8 y 1.9
Hello.instance_eval { @@foo } #=> uninitialized class variable
Hello.new.instance_eval { @@foo } #=> uninitialized class variable
Parece que las variables de clase no se buscan en 1.8 o 1.9 cuando se usa instance_eval
Lo que también es interesante es el caso de las constantes :
Constantes
class_eval
in Ruby 1.8
class Hello
Foo = :foo
end
Hello.class_eval { Foo } #=> uninitialized constant
class_eval
en Ruby 1.9
Hello.class_eval { Foo } #=> :foo
Entonces, al igual que con las variables de clase, las constantes se class_eval
en 1.9 pero no en 1.8 para class_eval
instance_eval
en Ruby 1.8
Hello.instance_eval { Foo } #=> uninitialized constant
Hello.new.instance_eval { Foo } #=> uninitialized constant
instance_eval
en Ruby 1.9
Hello.instance_eval { Foo } #=> uninitialized constant
Hello.new.instance_eval { Foo } #=> :foo
Parece que la búsqueda constante no es muy análoga a la búsqueda de variables de clase para Ruby 1.9. Una instancia de Hello
tiene acceso a la constante, mientras que la clase Hello
no lo hace.
Ruby 2.1
Esta es la forma más concisa y semánticamente correcta que he encontrado para acceder a una variable de clase:
class Hello
@@foo = :foo_value
end
Hello.class_variable_get :@@foo #=> :foo_value