問題のコード(再掲)はこれ。あるハッシュのキーについて繰り返しているのに、ハッシュにそのキーが存在しない。(すべてのキーが見つからないわけではないが、見つからないキーはいつでも見つからない)
categorized.keys.each do |c|
PStore.new(cache_file(c)).transaction do |db|
categorized.fetch(c) #=> key not found (KeyError)
db['category'] = {} unless db.root?('category')
db['category'].update(categorized[c])
end
end
fetchをブロックの最初に持って行くと、そこではエラーにならない。
categorized.keys.each do |c|
categorized.fetch(c) #=> O.K.
PStore.new(cache_file(c)).transaction do |db|
db['category'] = {} unless db.root?('category')
db['category'].update(categorized[c])
end
end
cache_file(c)の呼び出しが原因。その中でも includeしてある ERB::Utilの u()メソッドが核心。
categorized.keys.each do |c|
::ERB::Util.u(c)
categorized.fetch(c) #=> key not found (KeyError)
PStore.new(cache_file(c)).transaction do |db|
db['category'] = {} unless db.root?('category')
db['category'].update(categorized[c])
end
end
引数にした文字列のエンコーディングが変わってしまっている。
categorized.keys.each do |c|
enc1 = c.encoding;
::ERB::Util.u(c)
enc2 = c.encoding
categorized.fetch(c) { raise "#{enc1} #{enc2} #{::ERB.version}" } #=> UTF-8 ASCII-8BIT erb.rb [2.1.0 2009-01-11] (RuntimeError)
PStore.new(cache_file(c)).transaction do |db|
db['category'] = {} unless db.root?('category')
db['category'].update(categorized[c])
end
end
ERB::Util.url_encodeの定義を見ると、引数の文字列を dupした後にエンコーディングを変更しているにも関わらず、呼び出し元に影響を与えてしまっている。
def url_encode(s)
s.to_s.dup.force_encoding("ASCII-8BIT").gsub(/[^a-zA-Z0-9_\-.]/n) {
sprintf("%%%02X", $&.unpack("C")[0])
}
end
alias u url_encode
そんなわけだから呼び出し側(category.rb)で
u( c.dup )
なんてやっても効果はなく、
u( ""+c )
あるいは
u( "#{c}" )
とやって初めて、今回の現象を回避することができた。
これは、文字列の複製を遅らせた結果、期せずしておこった現象にみえる。
バグのはずなんだけど、irbで再現しようと思ってもできないんだこれが。
http://redmine.ruby-lang.org/issues/show/1929
(ここに、見るべき場所を見つけることもできなかった人間がひとり)
* 正しくは ruby-1.9.2dev(2009-02-03)