原谅我,伙计们。对于Ruby来说,我是最好的新手。我只是想知道对我来说看起来很奇怪的行为的解释。
我正在使用Savon库与我的Ruby应用程序中的SOAP服务进行交互。我注意到以下代码(在我编写的用于处理此交互的类中)似乎传递空值,其中我期望成员字段的值为:
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
尽管事实上@user
和@pass
都已初始化为非空字符串。
当我更改代码以使用本地代码时,它会按照我期望的方式工作:
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
我猜这个奇怪的(对我而言)行为必须与我在一个街区内的事实有关;但实际上,我不知道。有人可以在这个上启发我吗?
答案 0 :(得分:36)
首先,@user
不是Ruby中的“私有变量”;它是 instance variable 。实例变量在当前对象的范围内可用(self
指的是什么)。我已编辑了您问题的标题,以便更准确地反映您的问题。
块就像一个函数,一组代码将在以后执行。通常该块将在中定义块的范围内执行,但也可以在另一个上下文中评估该块:
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
因此,您要么在设置@user
的范围之外定义块,要么client.request
的实现导致稍后在另一个范围内评估该块。你可以写一下:
client.request("createSession"){ p [self.class,self] }
了解您的区块中当前self
的对象类型。
他们在你的情况下“消失”而不是抛出错误的原因是Ruby允许你请求任何实例变量的值,即使从未为当前对象设置了值。如果从未设置过该变量,那么您只需返回nil
(如果启用了它们,则会出现警告):
$ ruby -e "p @foo"
nil
$ ruby -we "p @foo"
-e:1: warning: instance variable @foo not initialized
nil
如您所见,块也是 closures 。这意味着当它们运行时,它们可以访问在定义块的同一范围内定义的局部变量。这就是您的第二组代码按预期工作的原因。闭包是一种很好的方法,可以锁定一个值,以便以后使用,例如在回调中。
继续上面的代码示例,您可以看到局部变量可用,无论评估块的范围如何,并且优先于该范围内的同名方法(除非您提供显式接收器):< / p>
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
答案 1 :(得分:5)
Savon :: Client.new接受一个块,您可以在其中访问本地变量甚至是您自己的类中的公共方法,但实例变量不起作用。如果您想知道为什么会这样,我建议您阅读有关委托的instance_eval。
当提出这个问题时,可能没有详细记录。
答案 2 :(得分:2)
在第一种情况下,self
计算为client.request('createSession')
,它没有这些实例变量。
在第二个中,变量作为闭包的一部分被带入块中。
答案 3 :(得分:2)
解决问题的另一种方法是将对象的引用带入块中,而不是多次枚举每个所需的属性:
o = self
create_session_response = client.request "createSession" do
soap.body = {
:user => o.user,
:pass => o.pass
}
end
但现在你需要属性访问器。