clases - codigo ruby ejemplo
¿Por qué las variables de instancia desaparecen aparentemente cuando están dentro de un bloque? (4)
De la documentación :
Savon :: Client.new acepta un bloque dentro del cual puede acceder a variables locales e incluso a métodos públicos desde su propia clase, pero las variables de instancia no funcionarán. Si quiere saber por qué, le recomiendo leer sobre instance_eval con delegación.
Posiblemente no tan bien documentado cuando se hizo esta pregunta.
Perdóname, chicos. Soy mejor novato cuando se trata de Ruby. Solo tengo curiosidad por saber la explicación de lo que me parece un comportamiento bastante extraño.
Estoy usando la biblioteca de Savon para interactuar con un servicio SOAP en mi aplicación Ruby. Lo que noté es que el siguiente código (en una clase que he escrito para manejar esta interacción) parece pasar valores vacíos donde espero que vayan los valores de los campos miembros:
create_session_response = client.request "createSession" do
soap.body = {
:user => @user, # This ends up being empty in the SOAP request,
:pass => @pass # as does this.
}
end
Esto es a pesar del hecho de que tanto @user
como @pass
se han inicializado como cadenas no vacías.
Cuando cambio el código para usar locales, funciona como espero:
user = @user
pass = @pass
create_session_response = client.request "createSession" do
soap.body = {
:user => user, # Now this has the value I expect in the SOAP request,
:pass => pass # and this does too.
}
end
Supongo que este extraño comportamiento (para mí) debe tener algo que ver con el hecho de que estoy dentro de un bloque; Pero en realidad, no tengo ni idea. ¿Podría alguien iluminarme en este caso?
En el primer caso, self
a client.request(''createSession'')
, que no tiene estas variables de instancia.
En el segundo, las variables se introducen en el bloque como parte del cierre.
En primer lugar, @user
no es una "variable privada" en Ruby; Es una variable de instancia . Las variables de instancia están disponibles dentro del alcance del objeto actual (lo que self
refiere a sí mismo). He editado el título de su pregunta para reflejar con mayor precisión su pregunta.
Un bloque es como una función, un conjunto de código que se ejecutará en una fecha posterior. A menudo, ese bloque se ejecutará en el ámbito donde se definió el bloque , pero también es posible evaluar el bloque en otro contexto:
class Foo
def initialize( bar )
# Save the value as an instance variable
@bar = bar
end
def unchanged1
yield if block_given? # call the block with its original scope
end
def unchanged2( &block )
block.call # another way to do it
end
def changeself( &block )
# run the block in the scope of self
self.instance_eval &block
end
end
@bar = 17
f = Foo.new( 42 )
f.unchanged1{ p @bar } #=> 17
f.unchanged2{ p @bar } #=> 17
f.changeself{ p @bar } #=> 42
Entonces, o bien está definiendo el bloque fuera del ámbito donde se establece @user
, o bien la implementación de client.request
hace que el bloque se evalúe en otro ámbito más adelante. Podrías averiguarlo escribiendo:
client.request("createSession"){ p [self.class,self] }
para obtener una idea de qué tipo de objeto es el self
actual en su bloque.
La razón por la que "desaparecen" en su caso, en lugar de lanzar un error, es que Ruby le permite solicitar el valor de cualquier variable de instancia, incluso si el valor nunca se ha establecido para el objeto actual. Si la variable nunca se ha establecido, solo volverá a nil
(y una advertencia, si las tiene habilitadas):
$ ruby -e "p @foo"
nil
$ ruby -we "p @foo"
-e:1: warning: instance variable @foo not initialized
nil
Como has encontrado, los bloques también son closures . Esto significa que cuando se ejecutan tienen acceso a las variables locales definidas en el mismo ámbito que se define el bloque. Es por eso que su segundo conjunto de código funcionó como se desea. Los cierres son una excelente manera de aferrarse a un valor para su uso posterior, por ejemplo, en una devolución de llamada.
Continuando con el ejemplo de código anterior, puede ver que la variable local está disponible independientemente del alcance en el que se evalúa el bloque y tiene prioridad sobre los métodos con el mismo nombre en ese alcance (a menos que proporcione un receptor explícito):
class Foo
def x
123
end
end
x = 99
f.changeself{ p x } #=> 99
f.unchanged1{ p x } #=> 99
f.changeself{ p self.x } #=> 123
f.unchanged1{ p self.x } #=> Error: undefined method `x'' for main:Object
Otra forma de solucionar el problema sería llevar una referencia a su objeto en el bloque en lugar de enumerar cada atributo necesario más de una vez:
o = self
create_session_response = client.request "createSession" do
soap.body = {
:user => o.user,
:pass => o.pass
}
end
Pero ahora necesitas atributos de acceso.