変数への代入に気をつける

Rubyの変数へのオブジェクトの代入は、オブジェクトへの参照ができるようになるだけ(オブジェクト自体はコピーされない)ということは知識としてはわかっていたのですが、いままでそのことを意識しなくても特に問題がなかったので、Cみたいにポインタ参照なのか実体なのかをちゃんと考えなくても、その辺りはRubyの方でよしなにやってくれてんじゃないのみたいに思っていたんですが、やっぱり問題になるときが来ました。

どういうケース?

Hashの中に Hashを追加したかったので、このようにやってみました。

  hkey = %w(itemPrice itemName)
  hs = Hash.new
  hc = Hash.new
  (@doc/"Item").each do |itm|
    hc.clear
    hkey.each { |k| hc[k] = (itm/"#{k}").text }
    hs[(itm/"itemCode").text] = hc;
  end
  hs

自分の目論見としては、これで hc には {"itemPrice"=>"7800", "itemName"=>"iP2600"} というようなデータがセットされて、hs には {"itemCode"=>"dpj:342", {"itemPrice"=>"7800", "itemName"=>"iP2600"}} というようなデータがセットされるんじゃないかなと思ってました。
ま、Itemが1件だとその通りにセットされるんですが、10件あると10件とも、"itemCode"はちゃんとしたキーがセットされますが、hcの中身の方は一番最後のデータがすべてにセットされてしまいます。
これは「hs[(itm/"itemCode").text] = hc」では hcへの参照(アドレス)がセットされるだけなので、hcの実体は1つしかないため、最後にセットした hcの中身が hsを見たときに見えてしまうからです。

正しくは?

こう(↓)するのが正しいです。

  hkey = %w(itemPrice itemName)
  hs = Hash.new
  (@doc/"Item").each do |itm|
    hc = Hash.new
    hkey.each { |k| hc[k] = (itm/"#{k}").text }
    hs[(itm/"itemCode").text] = hc;
  end
  hs

hcをその都度新しく作ってやる(newする)ことで、「hs[(itm/"itemCode").text] = hc」で代入する参照(アドレス)がそれぞれ異なることになります。あくまでも最初ののやり方でやるとすれば、「hs[(itm/"itemCode").text] = hc.dup」としてオブジェクトのコピーへの参照を代入してやればいいんですけど、dupでオブジェクトが新しく作られるんだったら、その都度 newしても同じじゃんということです。

どうして最初のやり方にしたのか

ループの中で毎回オブジェクトを newするともったいないよなと思って、ループの外で newしておいて、ループの中では clearでクリアしてやればいいと思ってました。hsに代入したときに hs自身のエリアが拡張されてオブジェクトそのものがコピーされるようなイメージです。ま、そういうことにならなかったわけですが。
でも、そうなると、ここで newした hsとか hcのライフサイクルっていつまでなんでしょうかね。
メソッドの戻り値が Hashで返るとして、戻り値はさすがに実体(オブジェクトのコピー)で返るんだったら、メソッドから抜ければ用無しになりますけど、戻り値が参照で返るとしたら、メソッド内で newされたオブジェクトは延々と生成され続けてメモリ上に残って・・・、なわけはないですから、メソッドからの戻り値はオブジェクトのコピー(実体)と思っていてセーフティですよね。(クラスのインスタンスとして保持されるのかな?)