为什么实例变量在块内部看似消失?

时间:2011-07-19 16:09:59

标签: ruby block instance-variables savon

原谅我,伙计们。对于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

我猜这个奇怪的(对我而言)行为必须与我在一个街区内的事实有关;但实际上,我不知道。有人可以在这个上启发我吗?

4 个答案:

答案 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)

来自the documentation

  

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

但现在你需要属性访问器。