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

代わりにローカルを使用するようにコードを変更すると、期待どおりに機能します。

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

ご覧のとおり、ブロックはクロージャでもあります。これは、実行時に、ブロックが定義されているのと同じスコープで定義されているローカル変数にアクセスできることを意味します。これが、2番目のコードセットが希望どおりに機能した理由です。クロージャは、コールバックなどで後で使用するために値をラッチするための優れた方法の1つです。

上記のコード例を続けると、ブロックが評価されるスコープに関係なくローカル変数が使用可能であり、そのスコープ内の同じ名前のメソッドよりも優先されることがわかります(明示的なレシーバーを指定しない限り)。

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

最初のケースでは、はにself評価されclient.request('createSession')ますが、これらのインスタンス変数はありません。

2番目の例では、変数はクロージャの一部としてブロックに取り込まれます。

于 2011-07-19T16:18:58.350 に答える
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 に答える