その他
    ホーム 技術発信 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サーバによっては、別途対応が必要な場合があるようです。