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:
Replacements
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.
ruby
## There's no way to ensure that the 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'
Rakefile
ruby
## Replace:
## require 'rake/rdoctask'
## with:
require 'rake/task'
config.ru
ruby
## Replace:
## require 'config/environment'
## with:
require File.dirname(__FILE__) + '/config/environment'
FasterCSV
➙CSV
Replace all
FasterCSV
constants with CSV
.
Also, include require 'csv'
to relevant files (or include the require
to your config/environment.rb
file).Inclusions
config/environment.rb
ruby
## Include this before the `Rails::Initializer.run` line:
if RUBY_VERSION >= '2.0.0'
module Gem
def self.source_index
sources
end
def self.cache
sources
end
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
end
end
end
config/initializers/paperclip.rb
ruby
## 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)
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
## 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)
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:
ruby
undefined local variable or method `vars' for # Rails::Generator::Commands::Create
(Thanks to Mr. S and jnwheeler44 for helping me to fix this one)
ruby
## 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)
end
end
end
end
RSpec
- Make sure you're using the last compatible version with
Rails 2.3.18
:
ruby
gem 'rspec', '1.3.2'
gem 'rspec-rails', '1.3.4'
-
Remove the file
script/spec
-
Remove the following lines from the
lib/tasks/rspec.rake
file:
ruby
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")
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
to1.9.x
.
ruby
# Replace:
when 'foo': bar
# with:
when 'foo' then bar
The behaviour for
protected
methods in new Ruby versions is a little bit different.
See more in this post.ruby
# In some cases, you might need to replace:
respond_to?(:foobar)
# with:
respond_to?(:foobar, true)
ruby
# Replace:
order: [ :day, :month, :year ]
# with:
order:
- :year
- :month
- :day
Ruby changes
- The default encoding for
Ruby 2.0
(or higher) isUTF-8
Remove all the code similar to:
ruby
# encoding: utf-8
Or:
ruby
$KCODE = 'UTF-8'
Update from 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!