Rubyで既存メソッドを拡張したい。拡張性も持たせたい。そんな時のやり方メモ。
例として Array#to_s
の拡張をする。
継承クラスでのメソッドオーバーライド
Arrayの継承クラスとsuper
を使う。Arrayが使われている他の部分を壊さないので安全。
Array#to_s
のように、本来なら引数を取らないメソッドを引数ありでオーバーライドする場合は、super()
と明示的に引数なしで呼ぶ必要がある。
class OreArray < Array
def to_s(args)
if args == :hello
"extend!!!"
else
super()
end
end
end
a = OreArray.new([1,2,3])
p a.to_s #=> "[1, 2, 3]"
p a.to_s(:hello) #=> "extend!!!"
クラスそのものへのモンキーパッチ
Array#to_s
そのものの動きを変更したい場合。
alias
や alias_method
を利用する。
class Array
alias_method :__to_s__, :to_s
# 拡張子したto_s
def to_s
'extend!!!!'
end
# 確認用
def to_s_origin
__to_s__
end
# 外部からアクセスできないようにする
private :__to_s__
end
a = [1,2,3]
p a.to_s #=> "extend!!!!"
p a.to_s_origin #=> "[1, 2, 3]"
p a.__to_s__ #=> `<main>': private method `__to_s__' called for [1, 2, 3]:Array (NoMethodError)
拡張性のある拡張をする
send
を使うと、モンキーパッチが当てやすくなる。
メソッドの命名規則を強要するのがメリットでもありデメリットでもある。
# Arrayの拡張
class Array
alias_method :__to_s__, :to_s
private :__to_s__
# 拡張子したto_s
def to_s(arg=nil)
if arg #arg.is_a?(Symbol) とするほうが明示的でいいかも?
send("to_s_#{arg}")
else
__to_s__
end
end
private
def to_s_join
join('-')
end
end
# Arrayの拡張の拡張
class Array
private
def to_s_reverse
reverse.to_s
end
end
a = [1, 2, 3]
p a.to_s #=> "[1, 2, 3]"
p a.to_s(:join) #=> "1-2-3"
p a.to_s(:reverse) #=> "[3, 2, 1]"
p a.to_s(:hogegege) #=> undefined method `to_s_hogegege' for [1, 2, 3]:Array (NoMethodError)
拡張性はないけどまとまりのある拡張
一度定義したら別にもう拡張しないし、と言う場合。
一箇所に処理がまとまって、読みやすい。Rails の Date#to_sはこのような拡張をしている。
class Array
alias_method :__to_s__, :to_s
private :__to_s__
TO_STRING_EXTEND = {
join: ->(arr){ arr.join('-') },
reverse: ->(arr){ arr.reverse.to_s },
}
# 拡張子したto_s
def to_s(arg=nil)
if method = ::Array::TO_STRING_EXTEND[arg]
method.call(self)
else
__to_s__
end
end
end
a = [1, 2, 3]
p a.to_s #=> "[1, 2, 3]"
p a.to_s(:join) #=> "1-2-3"
p a.to_s(:reverse) #=> "[3, 2, 1]"
p a.to_s(:hogegege) #=> "[1, 2, 3]" / to_sと同じ