Have a Rails 2 app? You can run it on the newest Ruby!


Do you have a legacy Rails application which is still running on Rails 2?

There are several reasons to migrate your application to new Rails versions, like to improve the security, to be able to use a better syntax, to take advantage of new features and also because most of current gems will only work on Rails 3 or higher. However, sometimes it’s hard to do that, especially for big projects. And certainly today there’re many project still running on Rails 2.

But there’s one good thing you can (and should) do! I’m talking about to use the newest Ruby version. Yes, I’m serious. When I wrote this post, the current Ruby version was 2.1.1 – and it’s not so hard to get it working fine with Rails 2.

Obviously, would be better if you have a good test coverage.

That said, let’s do it in a few steps:


1. Gemfile

Rails 2 apps don’t use Bundler by default, so if you don’t have Bundler managing your gems yet, you should check here how to do that.

# There's no way to ensure that next Ruby versions will work,
# but so far the current one works fine:
ruby '2.1.1'

# The same for rake:
rake '10.1.1'

# You might need the iconv gem:
gem 'iconv'

2. Rakefile

# Replace:
# require 'rake/rdoctask'

# with:
require 'rake/task'

3. config.ru

# Replace:
# require 'config/environment'

# with:
require File.dirname(__FILE__) + '/config/environment'

4. FasterCSV => CSV

Replace all FasterCSV constant with CSV. Also, include require 'csv' to relevant files (or include this require to config/environment.rb).


5. config/environment.rb

# Include this before the `Rails::Initializer.run` line:
if RUBY_VERSION >= '2.0.0'
  module Gem
    def self.source_index

    def self.cache

    SourceIndex = Specification

    class SourceList
      # If you want vendor gems, this is where to start writing code.
      def search(*args); []; end
      def each(&block); end
      include Enumerable

6. config/initializers/paperclip.rb

# The patches below are needed when using an old version of PaperClip + Ruby 2.x
# https://github.com/thoughtbot/paperclip/issues/262
# https://github.com/thoughtbot/paperclip/commit/1bcfc14388d0651c5fc70ab9ca3511144c698903

module Paperclip
  class Tempfile < ::Tempfile
    def make_tmpname(basename, n)
      extension = File.extname(basename)
      sprintf('%s,%d,%d%s', File.basename(basename, extension), $$, n.to_i, extension)

module IOStream
  def to_tempfile
    name = respond_to?(:original_filename) ? original_filename : (respond_to?(:path) ? path : 'stream')
    tempfile = Tempfile.new(['stream', File.extname(name)])

New files

7. config/initializers/ruby2.rb

# This is a very important monkey patch to make Rails 2.3.18 to work with Ruby 2+
# If you're thinking to remove it, really, don't, unless you know what you're doing.

if Rails::VERSION::MAJOR == 2 && RUBY_VERSION >= '2.0.0'
  module ActiveRecord
    module Associations
      class AssociationProxy
        def send(method, *args)
          if proxy_respond_to?(method, true)
            @target.send(method, *args)

8. config/initializers/rails_generators.rb

It’ll prevent Rails migration generator from stop working, otherwise you’ll receive the following error message:

undefined local variable or method `vars' for #<Rails::Generator::Commands::Create

(Thanks Mr. S and jnwheeler44 for helping me to fix this one)

# This is a very important monkey patch to make Rails 2.3.18 to work with Ruby 2+
# If you're thinking to remove it, really, don't, unless you know what you're doing.

if Rails::VERSION::MAJOR == 2 && RUBY_VERSION >= '2.0.0'
  require 'rails_generator'
  require 'rails_generator/scripts/generate'

  Rails::Generator::Commands::Create.class_eval do
    def template(relative_source, relative_destination, template_options = {})
      file(relative_source, relative_destination, template_options) do |file|
        # Evaluate any assignments in a temporary, throwaway binding
        vars = template_options[:assigns] || {}
        b = template_options[:binding] || binding
        # this no longer works, eval throws "undefined local variable or method `vars'"
        # vars.each { |k, v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
        vars.each { |k, v| b.local_variable_set(:"#{k}", v) }

        # Render the source file with the temporary binding
        ERB.new(file.read, nil, '-').result(b)


9. Make sure you’re using the last compatible version with Rails 2.3.18:

gem 'rspec', '1.3.2'
gem 'rspec-rails', '1.3.4'

10. Remove the file script/spec.

11. lib/tasks/rspec.rake

Remove all the following lines:

gem 'test-unit', '1.2.3' if RUBY_VERSION.to_f >= 1.9
rspec_gem_dir = nil

Dir["#{Rails.root}/vendor/gems/*"].each do |subdir|
  rspec_gem_dir = subdir if subdir.gsub("#{Rails.root}/vendor/gems/","") =~ /^(\w+-)?rspec-(\d+)/ && File.exist?("#{subdir}/lib/spec/rake/spectask.rb")

rspec_plugin_dir = File.expand_path(File.dirname(__FILE__) + '/../../vendor/plugins/rspec')

if rspec_gem_dir && (test ?d, rspec_plugin_dir)
  raise "\n#{'*'*50}\nYou have rspec installed in both vendor/gems and vendor/plugins\nPlease pick one and dispose of the other.\n#{'*'*50}\n\n"

if rspec_gem_dir
elsif File.exist?(rspec_plugin_dir)

Ruby syntax

12. Some details has been changed in Ruby syntax, especially from 1.8.x to 1.9.x.

Example 1:

# Replace:
when 'foo': bar

# with:
when 'foo' then bar

Example 2:

The behaviour for protected methods in new Ruby versions is a little bit different. See more in this post.

# In some cases, you might need to replace:

# with:
respond_to?(:foobar, true)

Example 3 (Yaml files):

# Replace:
order: [ :day, :month, :year ]

# with:
  - :year
  - :month
  - :day

Ruby changes

13. The default encoding for Ruby 2.0 (or higher) is UTF-8. So, remove all the code similar to:

# encoding: utf-8


$KCODE = 'UTF-8'

Important note (included on July 27, 2014)

Check below the comments of this post — Gabriel Sobrinho, Kyle Ries and Greg made some very interesting and useful comments.


Each project could have different issues.
But I hope this little guide helps you to use new Ruby versions in legacy Rails applications!

  • Greg

    First of all thanks a lot Lucas! This article is a great help. One more thing I would add is a monkey patch for the i18n library:

    module I18n

    module Backend

    module Base

    def load_file(filename)

    type = File.extname(filename).tr('.', '').downcase

    raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)

    data = send(:"load_#{type}", filename) # TODO raise a meaningful exception if this does not yield a Hash

    data.each { |locale, d| store_translations(locale, d) }






  • lucascaton

    Hey @disqus_NBg1NVhLuc:disqus, thanks for your collaboration.

    I didn’t have any issue with I18n so far, but it’s good to know about it. Probably it could be useful for someone else.


  • Gabriel Sobrinho

    Another important stuff, I have to change my custom autoload paths to include the rails root, which wasn’t necessary on ruby 1.8:

    config.autoload_paths += %W( #{RAILS_ROOT}/app/services )

  • lucascaton


  • Gabriel Sobrinho

    One more monkey patch, you need it if you have protected (before|after)_(create|update|save|validate) on your models:

    module ActiveRecord

    module Callbacks


    def callback(method)

    result = run_callbacks(method) { |result, object| false == result }

    # The difference is here, the respond_to must check protected methods.

    if result != false && respond_to_without_attributes?(method, true)

    result = send(method)



    return result




  • lucascaton

    Thanks dude!

  • Kyle Ries

    Thanks for the article! I’m curious – why is it necessary to remove the ./script/spec file? I seem to be getting a Segmentation Fault error when running my specs (though it seems my web server and console are running fine).

    Edit- Interestingly, the trace starts in ActiveRecord::Base, while #callback is being sent. The monkey patch from Gabriel doesn’t seem to solve the issue though.

  • Kyle Ries

    Hi Gabriel! How did you happen to come across this solution? I’m running into an issue currently and am wondering if your path to this solution might shed some light on it. :) Thanks!

  • lucascaton

    Hi Kyle,
    I’ve removed the `./script/spec` file just because the last RSpec version compatible with Rails 2.3.18 will already work without that.

  • Kyle Ries

    Appreciate the reply! I was hoping you were going to say “Actually, Kyle – I ran into the same problem, so…” ;-)

    I’ll keep plugging away at this and add some info here if I can come up w/ a solution. Anyhow – thanks again for the article!

  • lucascaton

    Hmm sorry, I’m not having the same issue…
    If you find the solution, I’d appreciate for sharing here.
    Good luck! ;-)

  • Gabriel Sobrinho

    Hey! Well, the active record callbacks just stopped to being called on our application and when we looked at active record source where the callbacks are called, we immediately saw the respond_to? call which changed the behaviour.

    That one was pretty easy to fix. Which issue are you running?

  • Kyle Ries

    Ah, I see. It turns out that it ended up being an issue w/ having the debugger gem in the environment while running the specs. It caused a seg fault when trying to initialize objects, specifically being raised when the after_initialize callback was being called. Removing the debugger gem solved it, though, to be frank – I wasn’t able to connect the dots to understand why debugger was the problem, though it seems somehow related to that method being aliased.

  • Kyle Ries

    Hi Lucas – see my convo w/ Gabriel below. I guess I’m not the first one to have issues w/ the dubugger gem and Ruby 2.1 – it may be worth including in your post. :-)

  • lucascaton

    Absolutely! I’ve just included a “Important note” in the post advising readers to check these comments. Thanks!

  • While running our app we find out that the password aren’t filtered in the logs anymore, so in order to fix it we made a new patch:


    hope that help anyone who has this issue.

  • lucascaton

    Thanks @felix_rafael:disqus ;)

  • Mr.S

    Thank you for putting this together. It helped to get the app running on Ruby 2.1.5 but not on 2.2.0.

  • lucascaton

    Hi Mr. S,
    I’m glad to know that this was helpful.

    Unfortunately even Rails 3 is not compatible with Ruby 2.2 :(

  • Mr.S

    In case it helps someone, I had a strange problem with Rails migration generator under 2.1.5. #eval was throwing “undefined local variable or method `vars’ for #<Rails::Generator::Commands::Create" error.

    Here's a monkey-patch. https://gist.github.com/anonymous/5742311bc8bb4ac5285e

  • lucascaton

    Thanks for it, I am having this problem!

    Though, the monkey-patck didn’t work for me, I’m getting a `uninitialized constant Rails::Generator` error now. Where did you put it?


  • Mr.S

    Rails::Generator is not always loaded, so putting it in the config/initializers wouldn’t work. I am not aware of the correct place; I had a custom generator installed as a plugin, so I just dropped the patch into that file.

  • lucascaton

    Hmm, I see. Which plugin are you using?

  • Mr.S

    I have https://rubygems.org/gems/hobofields vendorized as a plugin.

  • lucascaton


  • jnwheeler44

    I was able to get this to work in an initializer by adding these lines to the top of the file:

    require ‘rails_generator’
    require ‘rails_generator/scripts/generate’

  • lucascaton

    Amazing, it worked to me. Thanks a lot.

    Will update the post! :-)

  • jnwheeler44

    Great, glad I could lend a hand. Thank you for this post, it is very helpful!

  • Thanks!