目次
この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。
Ruby1.9 で Rails2.3 を動かす方法を紹介します。
■ 今回の環境
- Ruby1.9.1p376
- Rails2.3.5
■ はじめに
Ruby1.9 を Rails で動かす上で、一番頭を悩ませる問題が、
incompatible character encodings: ASCII-8BIT and UTF-8
というエラーかと思います。
Ruby1.9 から String に Encoding を持つようになりました。
これにより異なる Encoding 同士では、比較・結合を行うことができず、上記のようなエラーが発生してしまいます。
これは、Magic Comment で script encoding をUTF-8で指定しても、DB の Encode を UTF-8 に指定しても発生してしまいます。
これには色々と原因があるのですが、大きくは ActionView のレンダリング時に、ASCII-8BIT で出力される部分があるのが問題となります。
通常、Ruby1.9 でコーディングする際には、Magic Comment を記述し、script encoding を明示します。
この方法だけで、基本的な1ファイルでの View のレンダリングは問題なく行えます。
しかし、partial や layout を利用して、複数の View を組み合わせた場合に、問題が発生します。
ActionView は、内部的に読み込んだ View ファイルを buffer に格納し、順次結合していきますが、
buffer の Encoding が、partial した View と結合される際に、ASCII-8BIT となってしまうため、エラーが発生するようです。
■ ActionView にパッチを当てる
ということで、ActionView にパッチを当てます。
View ファイルを読み込み、順次結合してる部分に問題があるので、その部分を UTF-8 に Encode するように指定します。
パッチを当てる方法は何でもいいのですが、今回は alias_method_chain を使った方法で記述します。
下記コードをlib以下などに格納して、起動時に読み込まれるようにします。
action_view/renderable.rb
# -*- coding:utf-8 -*-
module ActionView
module Renderable
private
def compile_with_magic_comment!(render_symbol, local_assigns)
locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join
source = <<-end_src
# -*- coding:utf-8 -*-
def #{render_symbol}(local_assigns)
old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
ensure
self.output_buffer = old_output_buffer
end
end_src
begin
ActionView::Base::CompiledTemplates.module_eval(source, filename, 0)
rescue Errno::ENOENT => e
raise e # Missing template file, re-raise for Base to rescue
rescue Exception => e # errors from template code
if logger = defined?(ActionController) && Base.logger
logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
logger.debug "Function body: #{source}"
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
end
raise ActionView::TemplateError.new(self, {}, e)
end
end
alias_method_chain :compile!, :magic_comment
end
end
actionpack/lib/action_view/renderable.rb の68,69行目に Magic Comment を追加するだけです。
このパッチだけで、レンダリング時の Encoding の問題はほぼ解決できるのですが、
次に問題になってくるのが、日本語の文字列をフォームから GET で送信した場合です。
この場合は、
incompatible character encodings: UTF-8 and ASCII-8BIT
というエラーが発生します。
これは、送信されたデータを text_field などに再セットする場合に発生します。
UTF-8 の view に対して、ASCII-8BIT の文字列を結合しようとしているのがエラーの原因となります。
どうやらこの問題は Rack::Utils.unescape にあるようです。
Rack は、URL をデコードする際に、マルチバイト文字列を ASCII-8BIT として Encode してしまいます。
POST も同様に Form データの parse 時に同様の問題が発生しますが、GET と同じ対応で対処することは可能です。
ただし、POST の場合はこれだけでは解決せず、multipart 指定のフォームについては別途対処する必要があります。
multipart 用の parser である、Rack::Utils::Multipart.parse_multipart の戻り値の文字列は ASCII-8BIT となってしまうので、こちらも別途対処する必要があります。
上記の問題は、Rack にパッチを当てることで解決することは可能です。
ただし、この挙動自体は Rack としては正しい動作ではあります。
Encoding の問題はアプリケーション側で解決すべきで、Rackは関知すべきではないからです。
■ ActionControllerにパッチを当てる
ということで、上記の問題を解決するパッチを ActionController に当てます。
パッチの適用方法は ActionView と同様です。
action_controller/request.rb
# -*- coding:utf-8 -*-
module ActionController
class Request
private
def normalize_parameters_with_force_encoding(value)
(_value = normalize_parameters_without_force_encoding(value)).respond_to?(:force_encoding) ?
_value.force_encoding(Encoding::UTF_8) : _value
end
alias_method_chain :normalize_parameters, :force_encoding
end
end
actionpack/lib/action_controller/request.rb の472行目の normalize_parameters を修正します。
これにより、アプリケーションのコントローラでparamsを利用する時点で、リクエストパラメータに含まれる文字列は、UTF-8 に Encode されているので、view との結合も問題なく行うことができます。
■ 最後に
今回紹介した ActionView、ActionController を修正する方法で、Ruby1.9 上で Rails2.3 系をとりあえず動かすことはできそうです。
ただし、利用するプラグインやAPサーバによっては、別途対応が必要な場合があるようです。