(jpmobile の) mobile_filter hankaku => true は controller.response.body が freeze されてるとエラーになる
そもそも response.body が freeze された後に、文字変換すんなよというのは置いておいて。。。
例えば、
class HogeController < ApplicationController mobile_filter :hankaku => true around_filter :my_around_filter # mobile_filter は around_filter なので、filterの実行順は # mobile_filter(before) --> my_around_filter(before) --> action(index) --> # my_around_filter(after) --> mobile_filter(after) # # (mobile_filterは、正確にはオプション指定によるが、around_filter が1〜4つだが、 # ここではまとめて1つとして記述している) def index end private def my_around_filter # before filter では何もしない yield # after filter で、response.body を freeze response.body.freeze end end
これを実行すると、「can't modify frozen string」でエラーになる
エラー箇所は、「vendor/plugins/jpmobile/lib/jpmobile/filter.rb:103:in `gsub!'」
周辺のコードは、次のようになっている。
def filter(str, from, to) str = str.clone from.each_with_index do |int, i| str.gsub!(int, to[i]) # line. 103 end str end
str に response.body が入ってくるのだが、
freezeされているものを gsub! で(全角カナ --> 半角カナ に)破壊的に変更しようとしてエラーとなっている。
ちなみに、hankaku => true 無しだと、問題なく動作する
class HogeController < ApplicationController mobile_filter # 全角・半角変換無し around_filter :my_around_filter def index end private def my_around_filter # before yield # after response.body.freeze end end
これであっても、freeze 後に文字コードを変えているのだが、なぜこっちではエラーにならないのか。
結論から言うと、文字コード変更などでは、response.body そのものを変更しているのではなく、
response.body の文字列をコピーしたものを変更して、それをresponse.body に入れているからだと思われる。
frozen? でresponse.body の状態を出力させてみると
class HogeController < ApplicationController around_filter :my_around_filter1 # 確認用 mobile_filter # 全角・半角変換無し around_filter :my_around_filter2 # 実行順序は、 # my_around_filter1(before) --> mobile_filter(before) --> my_around_filter2(before) --> # action(index) --> # my_around_filter2(after) --> mobile_filter(after) --> my_around_filter1(after) def index end private def my_around_filter1 # before yield # after logger.debug("### #{response.body.frozen?}") end def my_around_filter2 # before yield # after response.body.freeze logger.debug("### #{response.body.frozen?}") end # ログは以下のようになり、mobile_filter で response.body の freeze 状態が解除されているのが確認できる # ### true # ### false end
で、全角・半角変換有りの話しに戻って、気になるのが、エラーが出ていた103行目の2行上にある「str = str.clone」
これは一体何のために存在しているのか。
もし、freezeされていない文字列のコピーを作りたいという考えであるとすれば、
cloneでは意図する動作になっていない。
プログラミング言語 Ruby リファレンスマニュアル の clone, dup の説明に書いてあるとおり、
clone では、freeze状態までコピーしてしまう。
試しに、101行目のcloneをdupに変更したら、問題なく動作した。
これがjpmobileの意図する通りの挙動なのか、バグなのかは分からないが、
全角・半角変換だけがfreezeされた場合にエラーになるのは、個人的には微妙な挙動だと思う。
response.bodyがfreezeされていた時にエラーにするなら、文字コード変換などでもエラーを出力し、
freezeされていてもfreezeを解除して実行してしまうのであれば、
全角・半角変換でもエラーにならないようになっていると、
mobile_filter全体として一貫性がある気がする。
ただ、文字コード変更や、絵文字のキャリア出し分けみたいな変換は、外見を変えるものではないのに対し、
全角・半角変換は外見からして変えるものだから、こういう挙動にしているんだと言われたら、
それはそれで納得できそうな気もする。