あるオブジェクト(obj変数)をコピーしたいとき
大別すると3種類の方法がありそう。
ruby破壊的操作の特徴については以下にもまとめました。
http://qiita.com/metheglin/items/ca452bc0a3866d2de7bf
変数の代入の場合
参照の値渡しになるので、コピー先の変数から変更操作をおこなうとコピー元のデータも変わってしまう。
メソッドの引数に変数を渡す場合も代入になる。
## 代入による変数のコピー
obj = { name: 'Edgar Allan Poe', age: 10 }
obj_assign = obj
## 変更をおこなう
obj_assign[:name] = 'Peter Ilyich Tchaikovsky'
## コピー先のデータだけでなくコピー元のデータも変更されてしまう
obj
# => {:name=>"Peter Ilyich Tchaikovsky", :age=>10}
obj_assign
# => {:name=>"Peter Ilyich Tchaikovsky", :age=>10}
変数のcloneの場合
clone(またはdup)を使うと参照の値渡しではなく、コピーしたいオブジェクトを新規オブジェクトにコピーし、その参照が保持される。そのため、コピー元データとコピー先データを独立にすることができる。
obj = { name: 'Edgar Allan Poe', age: 10 }
obj_clone = obj.clone
## 変更をおこなう
obj_clone[:name] = 'Peter Ilyich Tchaikovsky'
## コピー先のデータのみが変更され、コピー元のデータは維持される
obj
# => {:name=>"Edgar Allan Poe", :age=>10}
obj_clone
# => {:name=>"Peter Ilyich Tchaikovsky", :age=>10}
ただし、objが参照している要素に対して破壊的メソッドを使うとcloneしても元のデータが変わってしまう。
obj = { name: 'Edgar Allan Poe', age: 10 }
obj_clone = obj.clone
## replace(破壊的変更・オブジェクト自体を変更)によって変更をおこなう
obj_clone[:name].replace 'Peter Ilyich Tchaikovsky'
## コピー先のデータだけでなくコピー元のデータも変更されてしまう
obj
# => {:name=>"Peter Ilyich Tchaikovsky", :age=>10}
obj_clone
# => {:name=>"Peter Ilyich Tchaikovsky", :age=>10}
こういうことが起きるのはたぶんcloneが以下の図のような原理で動くから。
Marshalモジュールを使う場合
http://docs.ruby-lang.org/ja/2.1.0/class/Marshal.html
Marshalモジュールを使うと、オブジェクトがさらに参照するオブジェクトに対してもコピーしてくることができる。(深いコピー)
MarshalはjavascriptのJSONのようにオブジェクトの定義を文字列として出力したり、文字列からオブジェクトを復元したりするモジュールらしい。
obj = { name: 'Edgar Allan Poe', age: 10 }
obj_marshal = Marshal.load(Marshal.dump(obj))
## replace(破壊的変更・オブジェクト自体を変更)によって変更をおこなう
obj_marshal[:name].replace 'Peter Ilyich Tchaikovsky'
## コピー先のデータのみが変更され、コピー元のデータは維持される
obj
# => {:name=>"Edgar Allan Poe", :age=>10}
obj_marshal
# => {:name=>"Peter Ilyich Tchaikovsky", :age=>10}
実行環境
% ruby -v (git)-[develop]
ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-linux]