There is a way to have your old Rails 2.3.something
application running using latest Ruby from the 2.1
branch. However, it is moderately complex and requires quite a few hacks. Not everything works perfect with this setup, though. Common exceptions are performance tests and some of the generators, but I regard those as minor annoyances.
I’m using 2-3-stable
branch of Rails, which means version 2.3.18
plus some additional unreleased patches on top of it and a recently released Ruby 2.1.8
.
This guide assumes that you are using Bundler to manage your gems. If not, please follow this guide first.
Gemfile
gem 'rails', :github => 'rails/rails', :branch => '2-3-stable'
gem 'rake'
gem 'json'
gem 'iconv'
group :test do
gem 'test-unit', '1.2.3'
end
And then:
$ bundle update rails rake json iconv test-unit
Rakefile
Remove the following line:
require 'rake/rdoctask'
config/boot.rb
Change the following block after rescue Gem::LoadError => load_error
to look like this:
if load_error.message =~ /Could not find RubyGem rails/
STDERR.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
exit 1
else
raise
end
config/deploy.rb
Update your Ruby version used for Capistrano deployments. Only if you’re using Capistrano with RVM.
set :rvm_ruby_string, '2.1.8'
config/environment.rb
Change hardcoded Rails version:
RAILS_GEM_VERSION = '2.3.18' unless defined? RAILS_GEM_VERSION
Before Rails::Initializer.run
block add the following:
# Rails 2.3 and Ruby 2.0+ compatibility hack
# http://blog.lucascaton.com.br/index.php/2014/02/28/have-a-rails-2-app-you-can-run-it-on-the-newest-ruby/
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
Additional initializers
config/initializers/active_record_callbacks_ruby2.rb
:
# ActiveRecord::Callbacks compatibility fix
# http://blog.lucascaton.com.br/index.php/2014/02/28/have-a-rails-2-app-you-can-run-it-on-the-newest-ruby/
module ActiveRecord
module Callbacks
private
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)
end
notify(method)
return result
end
end
end
config/initializers/backport_3937.rb
:
# Encoding issues with Ruby 2+
# backport #3937 to Rails 2.3.8
# https://github.com/rails/rails/pull/3937/files
class ERB
module Util
def html_escape(s)
s = s.to_s
if s.html_safe?
s
else
silence_warnings { s.gsub(/[&"'><]/n) { |special| HTML_ESCAPE[special] }.html_safe }
end
end
end
end
config/initializers/i18n_ruby2.rb
:
# Patch to make i18n work with Ruby 2+
# http://blog.lucascaton.com.br/index.php/2014/02/28/have-a-rails-2-app-you-can-run-it-on-the-newest-ruby/
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) }
end
end
end
end
config/initializers/rails_generators.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.
# http://blog.lucascaton.com.br/index.php/2014/02/28/have-a-rails-2-app-you-can-run-it-on-the-newest-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|
# 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
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.
# http://blog.lucascaton.com.br/index.php/2014/02/28/have-a-rails-2-app-you-can-run-it-on-the-newest-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/string_encodings.rb
:
# Set default encoding for everything coming in and out of the app
# TODO this could/should be removed when upgrading to Rails 3+
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
config/initializers/utf8_params.rb
# convert all params into UTF-8 (from ASCII-8BIT)
# http://jasoncodes.com/posts/ruby19-rails2-encodings
raise "Check if this is still needed on " + Rails.version unless Rails.version == '2.3.18'
class ActionController::Base
def force_utf8_params
traverse = lambda do |object, block|
if object.kind_of?(Hash)
object.each_value { |o| traverse.call(o, block) }
elsif object.kind_of?(Array)
object.each { |o| traverse.call(o, block) }
else
block.call(object)
end
object
end
force_encoding = lambda do |o|
o.force_encoding(Encoding::UTF_8) if o.respond_to?(:force_encoding)
end
traverse.call(params, force_encoding)
end
before_filter :force_utf8_params
end
References: