問題のコード(再掲)はこれ。あるハッシュのキーについて繰り返しているのに、ハッシュにそのキーが存在しない。(すべてのキーが見つからないわけではないが、見つからないキーはいつでも見つからない)
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)