Do you have a legacy app still running on Rails 2?
There are several reasons to migrate your application to a newer version of Rails: improving security, using a better syntax, taking advantage of new features, and ensuring compatibility with most current gems, which typically require Rails 3 or higher. However, this process can be challenging, especially for large projects. It's worth noting that many projects are still running on Rails 2 today (in 2014).
But there's one important thing you can (and should) do! I'm talking about using the newest Ruby version. Yes, I'm serious. When I wrote this post, the latest Ruby version was
2.1.1, and it's not too difficult to get it working smoothly with Rails 2.
Obviously, it's better if you have good test coverage.
That said, let's break it down into a few steps:
Replacements
Rails 2 apps don't use Bundler by default. If you're not already using Bundler to manage your gems, you should
check this guide to set it up.
ruby
ruby "2.1.1"
rake "10.1.1"
gem "iconv"
ruby
require File.dirname(__FILE__) + "/config/environment"
Replace all FasterCSV
constants with CSV
.
Also, include require "csv"
to relevant files (or include the require
to your config/environment.rb
file).
Inclusions
ruby
if RUBY_VERSION >= "2.0.0"
module Gem
def self.source_index
sources
end
def self.cache
sources
end
SourceIndex = Specification
class SourceList
def search(*args); []; end
def each(&block); end
include Enumerable
end
end
end
config/initializers/paperclip.rb
ruby
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)
end
end
end
module IOStream
def to_tempfile
name = respond_to?(:original_filename) ? original_filename : (respond_to?(:path) ? path : "stream")
tempfile = Tempfile.new(["stream", File.extname(name)])
tempfile.binmode
self.stream_to(tempfile)
end
end
New files
config/initializers/ruby2.rb
ruby
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)
super
else
load_target
@target.send(method, *args)
end
end
end
end
end
end
config/initializers/rails_generators.rb
It'll prevent Rails migration generator from stop working, otherwise you'll receive the following error message:
rubyundefined local variable or method `vars' for # Rails::Generator::Commands::Create
ruby
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|
vars = template_options[:assigns] || {}
b = template_options[:binding] || binding
vars.each { |k, v| b.local_variable_set(:"#{k}", v) }
ERB.new(file.read, nil, "-").result(b)
end
end
end
end
RSpec
- Make sure you're using the last compatible version with
Rails 2.3.18
:
rubygem "rspec", "1.3.2"
gem "rspec-rails", "1.3.4"
rubygem "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")
end
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"
end
if rspec_gem_dir
$LOAD_PATH.unshift("#{rspec_gem_dir}/lib")
elsif File.exist?(rspec_plugin_dir)
$LOAD_PATH.unshift("#{rspec_plugin_dir}/lib")
end
Ruby syntax
- Ruby syntax has changed a bit, especially from
1.8.x
to 1.9.x
.
ruby
when "foo": bar
when "foo" then bar
The behaviour for
protected
methods in new Ruby versions is a little bit different.
See more in
this post.
ruby
respond_to?(:foobar)
respond_to?(:foobar, true)
ruby
order: [:day, :month, :year]
order:
- :year
- :month
- :day
Ruby changes
- The default encoding for
Ruby 2.0
(or higher) is UTF-8
Remove all the code similar to:
Or:
Update on July 27th, 2014
Check out the insightful comments below by Gabriel Sobrinho, Kyle Ries, and Greg. They offer valuable insights!
Conclusion
Different project might have different issues, but I hope this little guide helps you to use new Ruby versions on your legacy Rails applications!