Porting an application to Ruby 1.9

We recently ported our application to Ruby 1.9, here at escolinhas. After seeing the benefits of using Ruby 1.9.1 instead of 1.8.7, we just couldn’t resist (check before, after or just read the entire blog post).

We had to sort a few things out in order to make our application 100% compatible with Ruby 1.9. I’ll cover the issues that most people will probably have to face.

Encoding issues in Rails

Ruby 1.9 has a much more powerful encoding engine. Unfortunately, the developer needs to put some extra effort to be able to cope with this. Check this great blog post for an in depth analysis. I’ll assume that you are using UTF-8 on your project. If you aren’t, you really should.

First of all, here’s some code that you should place inside your config/initializers:

# coding: UTF-8

# TODO: Most of these issues are not present in Rails 3. Remove this when updating.

# Force mysql rows to be UTF-8 (see rails.lighthouseapp.com/projects/8994/tickets/2476)
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

# fix template rendering
module ActionView
  # NOTE: The template that this mixin is being included into is frozen
  # so you cannot set or modify any instance variables
  module Renderable #:nodoc:
    extend ActiveSupport::Memoizable


    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
        
        # Workaround for erb
        source.force_encoding('utf-8') if '1.9'.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

# the previous fix causes issues in uploaded files encoding, fixed here
module ActionController
  class Request
    private

      # Convert nested Hashs to HashWithIndifferentAccess and replace
      # file upload hashs with UploadedFile objects
      def normalize_parameters(value)
        case value
        when Hash
          if value.has_key?(:tempfile)
            upload = value[:tempfile]
            upload.extend(UploadedFile)
            upload.original_path = value[:filename]
            upload.content_type = value[:type]
            upload
          else
            h = {}
            value.each { |k, v| h[k] = normalize_parameters(v) }
            h.with_indifferent_access
          end
        when Array
          value.map { |e| normalize_parameters(e) }
        else
          value.force_encoding(Encoding::UTF_8) if value.respond_to?(:force_encoding)
          value
        end
      end
  end
end

So, what are we doing?

It’s not a good idea to be overriding certain Rails parts. You won’t need to do this when using Rails 2.3.6 (from what I’ve heard) and neither when using Rails 3 but for the present, where most of us use Rails 2.3.5, this is the way to go.

Setting the proper encoding in Ruby source files

Did you notice the following header on the intializer above?

# coding: UTF-8

I hope you did since you’ll be seeing it a lot unless you don’t use non-ascii in your Ruby files at all. If you do, you’ll need to specify its encoding. In many countries (like Portugal, Germany or China), the written language uses them all the time. To sort this out in our entire project without going mad, I created a simple task which handles this for us:

desc "Manage the encoding header of Ruby files"
task :check_encoding_headers => :environment do
  files = Array.new
  ["*.rb", "*.rake"].each do |extension|
    files.concat(Dir[ File.join(Dir.getwd.split(/\\/), "**", extension) ])
  end

  files.each do |file|
    content = File.read(file)
    next if content[0..16] == "# coding: UTF-8\n\n"
    
    ["\n\n", "\n"].each do |file_end|
      content = content.gsub(/(# encoding: UTF-8#{file_end})|(# coding: UTF-8#{file_end})|(# -*- coding: UTF-8 -*-#{file_end})/i, "")
    end

    new_file = File.open(file, "w")
    new_file.write("# coding: UTF-8\n\n"+content)
    new_file.close
  end
end

We run it once in a while, keeping our files clean and making sure that magical header is there. Not to worry if someone adds the header manually - the task is supposed to handle it, if necessary.

Minor issues

A few other minor issues also came up. I’ll just list some of them:

Final thoughts

As you’ve seen, most of the issues you’ll encounter when porting an application to Ruby 1.9 are encoding-related. We wouldn’t probably have these issues if UTF-8 was the default encoding for Ruby, but for now it’s ASCII-8BIT. 

Luckily, the Rails guys are trying to overcome these issues, along with the Ruby team. It’s nice to see a whole community working together.

Posted 1 year ago • Comments
blog comments powered by Disqus
Page 1 of 1