2011-06-15
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 ExtArrayArray)
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
わーい、…というかもっと良い方法はないんだろうか…
Ruby で HTTP の内容をトレースする
外部ライブラリを使ってテストを書くときや、デバッグ時に今どんな http のリクエストが送られてるかを知りたいとき、webmock を使うと知ることができる。
webmock は本来 http の stub つくるライブラリなんだけど、 allow_net_connect! と after_request を利用すると、実際のリクエストは出しつつも、結果をトレースすることができる。
require 'rubygems'
require 'webmock'
WebMock.allow_net_connect!
WebMock.after_request do |request_signature, response|
puts "= Request #{request_signature} was made ="
puts response.status.join(' ')
puts response.headers.map {|key, val| "#{key}: #{val}" }.join("\n")
puts "\n" + response.body unless response.body.empty?
end
require 'open-uri'
open('http://example.com/').read