代入メソッドの罠
Ruby ではメソッド名の末尾に = が付いていると、属性への代入のように使える。
def Foo def name=(name) @name = name end end foo = Foo.new # => #<Foo:0x100c7f98 @name=""> foo.name = 'foo' # => "foo" ←ここに注目 foo # => #<Foo:0x100c7f98 @name="foo">
つまり代入のように書くと Foo#name=(name) を呼び出してくれる。
しかし、Foo#name=(name) が self ではなく name を返してるっぽいのが罠。*1
foo = Foo.new # => #<Foo:0x100c6ff4 @name=""> name = foo.name = 'foo' # => "foo" foo # => #<Foo:0x100c6ff4 @name="foo"> name[0] = 'F' foo # => #<Foo:0x100c6ff4 @name="Foo">
foo の属性を外から変更できてしまう。
じゃあ、ちょっと不便になるけど Foo#name=(name) で self を返してやれば いいんじゃね?
class Foo def name=(name) @name=name self end end foo = Foo.new # => #<Foo:0x100de604 @name=""> foo.name = 'foo' # => "foo" ← なにそれこわい foo # => #<Foo:0x100de604 @name="foo">
代入式の値は左辺値ではなく右辺値らしい。*2
どうするか?
class Foo def name=(name) @name = Marshal.load(Marshal.dump(name)) end end foo = Foo.new # => #<Foo:0x100e10c8 @name=""> name = foo.name = 'foo' # => "foo" foo # => #<Foo:0x100e10c8 @name="foo"> name[0] = 'F' name # => "Foo" foo # => #<Foo:0x100e10c8 @name="foo">
浅いコピーではなく深いコピーをすることにした。*3