LoginSignup
38
30

More than 5 years have passed since last update.

あっけなくディープコピー on Rails

Posted at

ときおりハッシュや配列のディープコピーが必要になりますが、Rails標準で機能が用意してありました。

変数のコピー、シャローコピー

Rubyはオブジェクト指向の言語ですが、変数はオブジェクトへの参照となっているので、変数をコピーしても、オブジェクト自体は1つのままです。

変数のコピー
a = { foo: :bar }
b = a
b[:hoge] = :piyo

# 共通のオブジェクトなので、aも変わる
p a[:hoge] # => :piyo

そして、オブジェクトをコピーするためのメソッドとしてObject#dupがあります。これを使えば上のようなことはなくなりますが、まだ完全とはいい難いです。

シャローコピー
a = { foo: [1, 2, 3] }

# ハッシュのコピーを作る
b = a.dup

b[:hoge] = :piyo

# ハッシュは別になっているので、aには影響しない
p a[:hoge] # => nil

# a[:foo]の配列は共通のまま
b[:foo] << 4
p a[:foo].length # => 4

ということで、#dupでコピーされるのは自分自身のオブジェクトだけで、このような1階層のコピーをシャローコピー(shallow = 浅い)といいます。

ディープコピーするには~Ruby標準で~

それでは、中身までコピーするディープコピーを行うにはどうすればいいでしょうか。プレーンなRubyでは、いったんRubyのデータ構造を文字列にエンコード・デコードするMarshalモジュールを使って、以下のように書くというイディオムがあります。

Marshalを使ったディープコピー
b = Marshal.load(Marshal.dump(a))

これでも動くといえば動くのですが、ちょっと間接的というか、まどろっこしい感じがします。

Railsだともっとシンプル

Ruby on Rails(のActiveSupport)では、もっと単純な方法で実行できます。

ActiveSupport版
b = a.deep_dup

実装内容と制限

これがどのように実装されているか確認してみたところ、core_ext/object/deep_dup.rbという1ファイルになっていました。

  • dup不可能なもの(整数、truefalsenilSymbolなど)…そのまま
  • Array…各要素に#deep_dupを再帰適用してmap
  • Hash…まずハッシュ全体をdupして、(キーのdupが必要なら行いつつ)値を#deep_dup
  • それ以外…単純にdup

ということで、「ArrayHash以外のデータ構造」の場合は、自分で#deep_dupを実装しないと正しく実行できません。あと、再帰的なデータ構造を作り上げてしまった場合(Arrayの要素に自分自身を入れた場合など)、無限ループしてしまいます#deep_dupが便利なのは、ちょうどJSONから起こしたような、ハッシュ・配列の組み合わせ構造の場合に限られるようです。

38
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
30