variable rails define attribute ruby class-method instance-method

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.