Hatena::Groupsubtech

#生存戦略 、それは

-> 11 { 06 / 15 }

Ruby で Array を継承したクラスをうまくあつかう

19:07 | はてなブックマーク - Ruby で Array を継承したクラスをうまくあつかう - #生存戦略 、それは

Ruby で Array を継承/Mix-inしたクラスで、自分で定義した便利なメソッドを利用したい時ってありますよね。そんなとき普通に

class MyArray < Array
  def odd
    select {|f| f % 2 == 0 }
  end
end

と定義してうまくいく、と思いがちですが

ary = MyArray.new([1,2,3]).odd
p ary.class #=> MyArray であってほしいのにArray

となってしまいます。これは Ruby の実装で Array や Enumrator の配列を返す実装はその名の通り "Array" を返すため、自分が期待してる Array を継承してるクラスのインスタンスではなくなってしまっています。

継承がダメなら委譲、というわけで標準ライブラリの delegate.rb を使ってみようと考えます。delegate.rb のライブラリコードの一番下のサンプルに

class ExtArray<DelegateClass(Array)
  def initialize()
    super([])
  end
end 

ary = ExtArray.new
p ary.class
ary.push 25
p ary

というコードが書かれているので、おお、これはうまくいきそう!と思うんですがこれが罠で、配列を返すメソッドはもちろん Array のインスタンスが返ってきてしまい目的のことができないです。

なので全部のpublic なメソッドを上書きして、戻り値が Array の場合だけ自分自身を返すような module を作って Mix-in して解決!!1

module ArrayToSelfConvert
  def self.included(klass)
    methods = ::Array.public_instance_methods(true) - ::Kernel.public_instance_methods(false)
    methods |= ["to_s","to_a","inspect","==","=~","==="]
    methods.each {|method|
      define_method(method) {|*args, &block|
        res = super(*args, &block)
        if res.class == Array && method != 'to_a'
          cloned = deep_clone ? Marshal.load(Marshal.dump(self)) : self.dup
          cloned.clear.concat(res)
        else
          res
        end
      }
    }
  end
  attr_accessor :deep_clone
end
class MyArray < Array
  include ArrayToSelfConvert
  def odd
    select {|f| f % 2 == 0 }
  end
end
ary = MyArray.new([1,2,3]).odd
p ary.class #=> MyArray

わーい、…というかもっと良い方法はないんだろうか…

a2ikma2ikm2011/06/15 17:27ちょっと古めですけどNet::HTTPにロガーを仕込むnet-http-spyなんてのもありますよ
https://github.com/martinbtt/net-http-spy

secondlifesecondlife2011/06/15 19:02そんなライブラリがあったのですね、知りませんでした。
webmock のほうは、各種メジャーどころの http ライブラリのアダプターもあるので、Net::Http に限らず使えたりしますね。
https://github.com/bblimke/webmock/tree/master/lib/webmock/http_lib_adapters

トラックバック - http://subtech.g.hatena.ne.jp/secondlife/20110615