Почему переменные экземпляра, по-видимому, исчезают внутри блока? - PullRequest
26 голосов
/ 19 июля 2011

Простите, ребята.Я нахожусь в лучшем новичке, когда дело доходит до Руби.Мне просто любопытно узнать объяснение того, что мне кажется довольно странным.

Я использую библиотеку Savon для взаимодействия со службой SOAP в моем приложении Ruby.Я заметил, что следующий код (в классе, который я написал для обработки этого взаимодействия), кажется, передает пустые значения , где я ожидаю, что значения полей-членов будут идти:

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 ]

36 голосов
/ 19 июля 2011

Во-первых, @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
5 голосов
/ 28 ноября 2011

С документация :

Savon :: Client.new принимает блок, внутри которого вы можете получить доступ к локальным переменным и даже к открытым методам из вашего собственного класса, но переменные экземпляра не будут работать. Если вы хотите узнать, почему это так, я бы рекомендовал прочитать об instance_eval с делегированием.

Возможно, не так хорошо задокументировано, когда был задан этот вопрос.

2 голосов
/ 19 июля 2011

Другим способом решения этой проблемы было бы перенести ссылку на ваш объект в блок, а не перечислять каждый необходимый атрибут более одного раза:

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

Но теперь вам нужны атрибуты доступа.

2 голосов
/ 19 июля 2011

В первом случае self оценивается как client.request('createSession'), который не имеет этих переменных экземпляра.

Во втором переменные заносятся в блок как часть замыкания.

...