Ruby 1.9强大的编码问题

如果你用Ruby 1.9不会没有遇到过编码的问题吧,Ruby 1.9支持强大的m17n,强大到我一开始接触它都要疯掉了。。。

作为一个好屁民我决定把我的痛苦经历写一写,毕竟我还没见过这方面的中文介绍。Ruby是一种很特别的语言,Ruby 1.9更是个神奇的存在。我是用Ruby 1.9后才知道还有m17n这一说法的,看来我太寡聞了么

我遇到的错误,相信你也遇到过,除非你都用的ASCII码或者你比Ruby更怪

ActionView::Template::Error (incompatible character encodings: ASCII-8BIT and UTF-8)

伟大的革命精神是怎样的?克服一切困难,没有问题也要制造问题然后解决它。解决问题之前,先来介绍一点Ruby 1.9 encoding的处理方式。

谈到编码,自然就要讲字符串,Ruby 1.9中的字符串不是一个人,他已经被编码附体了!

一个String对象不仅包含着一般意义上的字符串,还有一个Encoding对象与它关联

irb(main):014:0> s = '中文'
=> "中文"
irb(main):015:0> s.encoding
=> #<Encoding:UTF-8>

而编码问题一般发生在字符串连接时,让我们践行伟大的无产阶级革命精神创造麻烦吧

irb(main):026:0> s.force_encoding('BINARY')
=> "\xE4\xB8\xAD\xE6\x96\x87"
irb(main):027:0> x = '也是中文'
=> "也是中文"
irb(main):028:0> x.encoding
=> #<Encoding:UTF-8>
irb(main):029:0> s << x
Encoding::CompatibilityError: incompatible character encodings: ASCII-8BIT and UTF-8

麻烦已经被我们制造出来了,可以看到与上面的错误是一样的,我们要做的就是解决掉它。

首先找到问题代码的位置在action_view/template/handlers/erb.rb,我们写一个initializer覆盖它。

# config/initializers/rails/action_view/template/handlers/erb.rb
require "action_view/template/handlers/erb"

ActionView::OutputBuffer.class_eval do
  undef << if defined?(:<<)

  def <<(value)
    self.force_encoding(Encoding.default_external)
    value = value.to_s.force_encoding(Encoding.default_external)
    super(value)
  end
  alias :append= :<<
end

代码意思很明确,在erb需要拼接时把双方都转化为Encoding.default_external,这个在Rails里默认是UTF-8的。

问题解决了吗?不!革命一定要彻底,把能找到的隐患全部消灭。数据库里读取数据自然不会少了字符串,编码也一定如影随形

# config/initializers/rails/active_record/connection_adapters/sqlite_adapter.rb
require 'active_record/connection_adapters/sqlite_adapter'

module ActiveRecord
  module ConnectionAdapters
    SQLiteAdapter.class_eval do
      undef select if defined?(:select)

      protected
      def select(sql, name = nil)
        execute(sql, name).map do |row|
          record = {}
          row.each do |k, v|
            if k.is_a?(String)
              v.force_encoding(Encoding.default_external) if v.respond_to?(:force_encoding)
              record[k.sub(/^"?\w+"?\./, '')] = v
            end
          end
          record
        end
      end

    end
  end
end

我可是sqlite3的坚决拥趸,自然就只顾着sqlite3了。

另一个问题是表单提交的信息,只要有字符串产生的地方就有编码隐患,消灭它

# config/initializers/rails/action_dispatch/http/parameters.rb
require 'action_dispatch/http/parameters'

module ActionDispatch
  module Http
    module Parameters
      undef normalize_parameters if defined?(:normalize_parameters)

      private
      def normalize_parameters(value)
        case value
        when Hash
          h = {}
          value.each { |k, v| h[k] = normalize_parameters(v) }
          h.with_indifferent_access
        when Array
          value.map { |e| normalize_parameters(e) }
        else
          value.force_encoding(Encoding.default_external)
        end
      end
    end
  end
end

多谢呼呼的提示,到最后我还是采纳了他的方案,现在问题暂时被解决了。

当然这些作案方式都是些很无耻的写法,如果一切都这么简单,Rails开发小组是不会把问题留给我的,嘻哈。

至于Ruby为什么选用现在的m17n,而不是像其他大部分语言在内部统一的使用一种编码,比如UTF-8。据说是为了方便不同编码的程序员搞基顺利?为了给程序员选择的自由?啊,我宁愿不要这种自由

想深入了解,这是对字符编码和Ruby字符处理的一系列文章Understanding M17n
另外还有Encodings, UnabridgedRuby 1.9 Encodings: A Primer and the Solution for Rails

TAGS:Rails,Ruby

10 COMMENTS >>LEAVE<<

  1. wliment

    ruby 的这个end 看着有点怪不协调

  2. ABitNo
    @wliment

    一般情况还是很协调的,不过现在是因为深深的一层里面只一个方法。。

  3. wliment

    学ruby ,python 有冲突吧 有时间想看看 ,又怕学乱了

  4. ABitNo
    @wliment

    能有什么冲突,放心的学吧。。。

  5. Firm

    额,我也是折腾了好几天才会的

  6. cake

    m17n最早是用在emacs的邮件客户端,支援国际化比较好的,这个MUA也是matz开发的,我想跟开发者的喜好有关,呵呵。

  7. ABitNo
    @cake

    m17n理解后发现是很棒的

    你是第二个被akismet误判为spam的--

  8. huy

    被这编码折磨一晚上
    比起 那么辛苦地对着翻译工作爬英文网站
    楼主辛苦了,汉字太亲切了.

  9. changwu

    终于解决了这个问题,谢谢博主。。。

  10. ahtest

LEAVE A RESPONSE >>CANCEL<<

loader