Wednesday, December 30, 2009

Migrer vers ruby 1.9

Je viens de migrer deux applications Rails 2.3.5 vers Ruby 1.9.1 avec deux problèmes majeurs concernant les encodings. Bien que Ruby soit unicode par défaut, pas mal de gems ne parlent pas l'UTF-8 par défaut...

Premier soucis avec ERB qui compile les templates en utilisant ASCII-8BIT par défaut. Cela génère l'erreur suivante:
ActionView::TemplateError (incompatible character encodings: ASCII-8BIT and UTF-8)

Il y a pas mal de discussions sur le sujet ici: https://rails.lighthouseapp.com/projects/8994/tickets/2188-i18n-fails-with-multibyte-strings-in-ruby-19-similar-to-2038

Une solution hacky à coup de monkey patch est de créer un fichier lib/actionview_utf8 comme ceci (c'est la ligne avec le source.force_encoding qui est rajoutée au code original):

module ActionView
  module Renderable #:nodoc:
    private
      def compile!(render_symbol, local_assigns)
        locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join

        source = <<-end_src
          def #{render_symbol}(local_assigns)
            old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
          ensure
            self.output_buffer = old_output_buffer
          end
        end_src
        source.force_encoding(Encoding::UTF_8) if source.respond_to?(:force_encoding)

        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
  end
end

puis d'ajouter les lignes suivantes à environnement.rb:

Rails::Initializer.run do |config|
  ...
  config.after_initialize do 
    require 'lib/actionview_utf8'
  end
end

Ensuite je suis tombé sur le problème des adaptateurs aux bases de données qui retournent de l'ASCI-8BIT au lieu d'UTF-8 à la lecture des données. Une solution du même acabit est décrite ici: http://gnuu.org/2009/11/06/ruby19-rails-mysql-utf8/. Ceci dit ça ne fonctionne qu'avec MySql.

Donc on créé le fichier lib/mysql_utf8:

require "mysql"

class Mysql::Result
  def encode(value, encoding = "utf-8")
    String === value ? value.force_encoding(encoding) : value
  end

  def each_utf8(&block)
    each_orig do |row|
      yield row.map {|col| encode(col) }
    end
  end
  alias each_orig each
  alias each each_utf8

  def each_hash_utf8(&block)
    each_hash_orig do |row|
      row.each {|k, v| row[k] = encode(v) }
      yield(row)
    end
  end
  alias each_hash_orig each_hash
  alias each_hash each_hash_utf8
end

et pour environnement.rb on se retrouve avec:

Rails::Initializer.run do |config|
  ...
  config.after_initialize do 
    require 'lib/actionview_utf8'
    require 'lib/mysql_utf8'
  end
end

ouf, tous les tests passent...

No comments:

Post a Comment