Подтвердить что ты не робот

Почему переменные экземпляра кажутся исчезающими, когда внутри блока?

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

Я использую библиотеку 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

Я предполагаю, что это странное (для меня) поведение должно иметь какое-то отношение к тому факту, что я внутри блока; но на самом деле, я понятия не имею. Может ли кто-нибудь просветить меня на этом?

4b9b3361

Ответ 1

Во-первых, @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

Ответ 2

Из документации:

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

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

Ответ 3

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

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

Ответ 4

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

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

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