ruby - rails - ¿Cómo entender la diferencia entre class_eval() y instance_eval()?
ruby instance attributes (5)
Foo = Class.new
Foo.class_eval do
def class_bar
"class_bar"
end
end
Foo.instance_eval do
def instance_bar
"instance_bar"
end
end
Foo.class_bar #=> undefined method ‘class_bar’ for Foo:Class
Foo.new.class_bar #=> "class_bar"
Foo.instance_bar #=> "instance_bar"
Foo.new.instance_bar #=> undefined method ‘instance_bar’ for #<Foo:0x7dce8>
Basándome en el nombre de los métodos, esperaría que class_eval te permita agregar un método de clase a Foo y instance_eval para permitirte agregar un método de instancia a Foo. Pero parecen hacer lo contrario .
En el ejemplo anterior, si llama a class_bar en la clase Foo, obtendrá un error de método no definido y si llama a instance_bar en la instancia devuelta por Foo.new, también obtendrá un error de método no definido. Ambos errores parecen contradecir una comprensión intuitiva de lo que deberían hacer class_eval y instance_eval.
¿Cuál es realmente la diferencia entre estos métodos?
Documentación para class_eval :
mod.class_eval (cadena [, nombre de archivo [, lineno]]) => obj
Evalúa la cadena o el bloque en el contexto de mod. Esto se puede utilizar para agregar métodos a una clase.
Documentación para instance_eval :
obj.instance_eval {| | bloque} => obj
Evalúa una cadena que contiene el código fuente de Ruby, o el bloque dado, dentro del contexto del receptor (obj). Para establecer el contexto, la variable self se establece en obj mientras se ejecuta el código, lo que le da acceso al código a las variables de instancia de obj.
Como dice la documentación, class_eval
evalúa la cadena o el bloque en el contexto del Módulo o Clase. Así que las siguientes piezas de código son equivalentes:
class String
def lowercase
self.downcase
end
end
String.class_eval do
def lowercase
self.downcase
end
end
En cada caso, la clase String ha sido reabierta y se ha definido un nuevo método. Ese método está disponible en todas las instancias de la clase, por lo que:
"This Is Confusing".lowercase
=> "this is confusing"
"The Smiths on Charlie''s Bus".lowercase
=> "the smiths on charlie''s bus"
class_eval
tiene una serie de ventajas sobre la simple reapertura de la clase. En primer lugar, puede llamarlo fácilmente en una variable, y está claro cuál es su intención. Otra ventaja es que fallará si la clase no existe. Por lo tanto, el siguiente ejemplo fallará ya que la Array
se deletrea incorrectamente Si la clase simplemente se reabriera, tendría éxito (y se definiría una nueva clase Aray
incorrecta):
Aray.class_eval do
include MyAmazingArrayExtensions
end
Finalmente, class_eval
puede tomar una cadena, lo que puede ser útil si estás haciendo algo un poco más nefasto ...
instance_eval
por otro lado, evalúa el código contra una única instancia de objeto:
confusing = "This Is Confusing"
confusing.instance_eval do
def lowercase
self.downcase
end
end
confusing.lowercase
=> "this is confusing"
"The Smiths on Charlie''s Bus".lowercase
NoMethodError: undefined method ‘lowercase’ for "The Smiths on Charlie''s Bus":String
Así que con instance_eval
, el método solo se define para esa instancia única de una cadena.
Entonces, ¿por qué instance_eval
en una Class
define métodos de clase?
Así como "This Is Confusing"
y "The Smiths on Charlie''s Bus"
son ambas instancias de String
, Array
, String
, Hash
y todas las demás clases son en sí mismas instancias de Class
. Puedes verificar esto llamando a #class
en ellos:
"This Is Confusing".class
=> String
String.class
=> Class
Entonces, cuando llamamos instance_eval
, hace lo mismo en una clase como lo haría en cualquier otro objeto. Si usamos instance_eval
para definir un método en una clase, definirá un método para esa instancia de clase, no todas las clases. Podríamos llamar a ese método un método de clase, pero es solo un método de instancia para esa clase en particular.
Creo que lo entendiste mal. class_eval agrega el método a la clase, por lo que todas las instancias tendrán el método. instance_eval agregará el método solo a un objeto específico.
foo = Foo.new
foo.instance_eval do
def instance_bar
"instance_bar"
end
end
foo.instance_bar #=> "instance_bar"
baz = Foo.new
baz.instance_bar #=> undefined method
La otra respuesta es correcta, pero permíteme profundizar un poco.
Ruby tiene varios tipos diferentes de alcance; De acuerdo con la wikipedia , seis, aunque parece faltar documentación formal detallada. Los tipos de alcance implicados en esta pregunta son, como es lógico, instancia y clase .
El ámbito de la instancia actual está definido por el valor de self
. Todas las llamadas a métodos no calificados se envían a la instancia actual, al igual que cualquier referencia a variables de instancia (que se parecen a @this
).
Sin embargo, def
no es una llamada de método. El objetivo para los métodos creados por def
es la clase actual (o módulo), que se puede encontrar con Module.nesting[0]
.
Veamos cómo los dos sabores de evaluación diferentes afectan estos ámbitos:
String.class_eval { [self, Module.nesting[0]] } => [String, String] String.instance_eval { [self, Module.nesting[0]] } => [String, #<Class:String>]
En ambos casos, el ámbito de la instancia es el objeto al que se llama * _eval.
Para class_eval
, el alcance de la clase también se convierte en el objeto de destino, por lo que def
crea métodos de instancia para esa clase / módulo.
Para instance_eval
, el alcance de la clase se convierte en la clase singleton (también conocida como metaclass, eigenclass) del objeto de destino. Los métodos de instancia creados en la clase singleton para un objeto se convierten en métodos singleton para ese objeto. Los métodos Singleton para una clase o módulo son lo que comúnmente se denominan métodos de clase .
El alcance de la clase también se utiliza para resolver constantes. Las variables de clase ( @@these @@things
) se resuelven con el alcance de la clase, pero se saltan las clases singleton cuando se busca la cadena de anidamiento de módulos. La única forma que encontré para acceder a las variables de clase en clases singleton es con class_variable_get/set
.
instance_eval crea efectivamente un método singleton para la instancia de objeto en cuestión. class_eval creará un método normal en el contexto de la clase dada, disponible para todos los objetos de esa clase.
Aquí hay un enlace con respecto a los métodos singleton y el patrón singleton (no específico de ruby)
instance_eval
y class_eval
permiten ejecutar una porción de código. Entonces, ¿qué podrías decir? La eval
pasada de moda puede hacer esto. Pero instance_eval
y class_eval
aceptan un argumento de bloque para la porción de código. Así que el trozo de código no necesita ser una cadena. También instance_eval
y class_eval
permiten un receptor (a diferencia de la eval
antigua). En consecuencia, puede invocar estos dos métodos modernos en un objeto de clase o incluso en un objeto de instancia.
class A
end
A.instance_eval do
# self refers to the A class object
self
end
a = A.new
a.instance_eval do
# self refers to the a instance object
self
end
También recuerde en ruby si llamamos a un método sin receptor, entonces el método se invocará en self
, que en el bloque instance_eval
es el objeto que invocamos instance_eval
. Las variables de instancia son privadas en ruby. No puede acceder a ellos fuera de la clase en la que están definidos. Pero como las variables de instancia se almacenan en self
, podemos acceder a ellas en instance_eval
(lo mismo se aplica a los métodos privados que no pueden invocarse con un receptor):
class A
def initialzie
@a = “a”
end
private
def private_a
puts “private a”
end
end
a = A.new
puts a.instance_eval { @a }
# => “a”
puts a.instance_eval { private_a }
# => “private a”
También podemos agregar métodos al receptor en instance_eval
y class_eval
. Aquí lo agregamos a instance_eval
:
class A
end
A.instance_eval do
def a_method
puts “a method”
end
end
A.a_method
# => a method
Ahora piensa lo que acabamos de hacer por un momento. Usamos instance_eval
, definimos un método en su block
y luego invocamos el método en el objeto de clase en sí. ¿No es eso un método de clase? Piense en ello como un método de "clase" si lo desea. Pero todo lo que hicimos fue definir un método en el receptor en el bloque instance_eval
, y el receptor resultó ser A
Podemos hacer fácilmente lo mismo en un objeto de instancia:
a.instance_eval do
def a_method
puts "a method"
end
end
a.a_method
# => a method
Y funciona igual. No piense en los métodos de clase como métodos de clase en otros idiomas. Son solo métodos definidos en self
, cuando el self
pasa a ser un objeto de clase (que se extiende desde Class.new
hasta el class A end
).
Pero quiero tomar esta respuesta un poco más profunda que la respuesta aceptada. ¿Dónde realmente instance_eval
pega los métodos que colocas en ellos? ¡Entran en la clase singleton
del receptor! Tan pronto como invoca instance_eval
en el receptor, el intérprete de ruby abre singleton_class
y coloca los métodos definidos en el bloque dentro de singleton_class
. ¡Es como usar extend
en una clase (porque extender abre la clase singleton y coloca los métodos en el módulo pasado para extenderse a la clase singleton)! Abre el singleton_class
, que forma parte de la jerarquía de herencia (justo antes de la clase padre): A -> singleton_class -> Parent
Ahora, ¿qué hace diferente a class_eval
? class_eval
solo puede ser llamado en clases y módulos. self
todavía se refiere al receptor:
class A
end
A.class_eval do
# self is A
self
end
Pero a diferencia de instance_eval
, cuando define métodos en el bloque class_eval
, estarán disponibles en las instancias de la clase y no en el objeto de la clase en sí. Con class_eval
, los métodos no se agregan a la clase singleton en la jerarquía de herencia. En su lugar, los métodos se agregan a la current class
del receptor! Entonces, cuando define un método en class_eval
, ese método va directamente a la current class
y, por lo tanto, se convierte en un método de instancia. Así que no puedes llamarlo en el objeto de clase; solo puedes llamarlo en instancias del objeto de clase.