28

原谅我,伙计们。说到 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都已被初始化为非空字符串。

当我将代码更改为使用 locals 时,它按我期望的方式工作:

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

4 回答 4

37

首先,@user不是 Ruby 中的“私有变量”;它是一个实例变量。实例变量在当前对象的范围内可用(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

如您所见,块也是闭包。这意味着当它们运行时,它们可以访问定义在与定义块相同的范围内的局部变量。这就是您的第二组代码按预期工作的原因。闭包是一种很好的锁定值以供以后使用的方法,例如在回调中。

继续上面的代码示例,您可以看到无论在哪个范围内评估块,局部变量都是可用的,并且优先于该范围内的同名方法(除非您提供显式接收器):

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
于 2011-07-19T16:54:06.623 回答
5

文档中

Savon::Client.new 接受一个块,您可以在其中访问局部变量甚至是您自己的类中的公共方法,但实例变量不起作用。如果您想知道为什么会这样,我建议您阅读有关带有委托的 instance_eval 的内容。

当问这个问题时,可能没有很好的记录。

于 2011-11-28T13:05:05.833 回答
2

解决此问题的另一种方法是将对您的对象的引用携带到块中,而不是多次枚举每个需要的属性:

o = self
create_session_response = client.request "createSession" do
  soap.body = {
    :user => o.user,
    :pass => o.pass
  }
end

但是现在您需要属性访问器。

于 2011-07-19T16:26:27.150 回答
2

在第一种情况下,self计算结果为client.request('createSession'),它没有这些实例变量。

第二,变量作为闭包的一部分被带入块中。

于 2011-07-19T16:18:58.350 回答