ホーム DoRuby Ruby1.9でRailsる

Ruby1.9でRailsる

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Ruby1.9 で Rails2.3 を動かす方法を紹介します。

 今回の環境

  1. Ruby1.9.1p376
  2. 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サーバによっては、別途対応が必要な場合があるようです。

記事を共有

最近人気な記事