Skip to content

Instantly share code, notes, and snippets.

@cypriss
Last active June 18, 2018 11:57
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save cypriss/1976864 to your computer and use it in GitHub Desktop.
Save cypriss/1976864 to your computer and use it in GitHub Desktop.
Rails 2.3.14 Ruby 1.9.3 - Monkey Patches

How we upgraded UserVoice, a Rails 2.3.14 app, to Ruby 1.9.3.

Blog post here

# At the top of environment.rb, set your default encodings:
Encoding.default_external = Encoding.default_internal = Encoding::UTF_8
# This little guy is needed unless you want to define a helper for every single one of your controllers
MissingSourceFile::REGEXPS << [/^cannot load such file -- (.+)$/i, 1]
# TZInfo needs to be patched. In particular, you'll need to re-implement the datetime_new! method:
require 'tzinfo'
module TZInfo
# Methods to support different versions of Ruby.
module RubyCoreSupport #:nodoc:
# Ruby 1.8.6 introduced new! and deprecated new0.
# Ruby 1.9.0 removed new0.
# Ruby trunk revision 31668 removed the new! method.
# Still support new0 for better performance on older versions of Ruby (new0 indicates
# that the rational has already been reduced to its lowest terms).
# Fallback to jd with conversion from ajd if new! and new0 are unavailable.
if DateTime.respond_to? :new!
def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
DateTime.new!(ajd, of, sg)
end
elsif DateTime.respond_to? :new0
def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
DateTime.new0(ajd, of, sg)
end
else
HALF_DAYS_IN_DAY = rational_new!(1, 2)
def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
# Convert from an Astronomical Julian Day number to a civil Julian Day number.
jd = ajd + of + HALF_DAYS_IN_DAY
# Ruby trunk revision 31862 changed the behaviour of DateTime.jd so that it will no
# longer accept a fractional civil Julian Day number if further arguments are specified.
# Calculate the hours, minutes and seconds to pass to jd.
jd_i = jd.to_i
jd_i -= 1 if jd < 0
hours = (jd - jd_i) * 24
hours_i = hours.to_i
minutes = (hours - hours_i) * 60
minutes_i = minutes.to_i
seconds = (minutes - minutes_i) * 60
DateTime.jd(jd_i, hours_i, minutes_i, seconds, of, sg)
end
end
end
end
# Finally, we have this innocuous looking patch. Without it, queries like this: current_account.tickets.recent.count
# would instantiate AR objects all (!!) tickets in the account, not merely return a count of the recent ones.
# See https://rails.lighthouseapp.com/projects/8994/tickets/5410-multiple-database-queries-when-chaining-named-scopes-with-rails-238-and-ruby-192
# (The patch in that lighthouse bug was not, in fact, merged in).
module ActiveRecord
module Associations
class AssociationProxy
def respond_to_missing?(meth, incl_priv)
false
end
end
end
end
# Make sure the logger supports encodings properly.
module ActiveSupport
class BufferedLogger
def add(severity, message = nil, progname = nil, &block)
return if @level > severity
message = (message || (block && block.call) || progname).to_s
# If a newline is necessary then create a new message ending with a newline.
# Ensures that the original message is not mutated.
message = "#{message}\n" unless message[-1] == ?\n
buffer << message.force_encoding(Encoding.default_external)
auto_flush
message
end
end
end
# This makes it so all parameters get converted to UTF-8 before they hit your app. If someone sends invalid UTF-8 to your server, raise an exception.
# At UserVoice, we rescue this exception and show a custom error page.
class ActionController::InvalidByteSequenceErrorFromParams < Encoding::InvalidByteSequenceError; end
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|
if o.respond_to?(:force_encoding)
o.force_encoding(Encoding::UTF_8)
raise ActionController::InvalidByteSequenceErrorFromParams unless o.valid_encoding?
end
if o.respond_to?(:original_filename)
o.original_filename.force_encoding(Encoding::UTF_8)
raise ActionController::InvalidByteSequenceErrorFromParams unless o.original_filename.valid_encoding?
end
end
traverse.call(params, force_encoding)
path_str = request.path.to_s
if path_str.respond_to?(:force_encoding)
path_str.force_encoding(Encoding::UTF_8)
raise ActionController::InvalidByteSequenceErrorFromParams unless path_str.valid_encoding?
end
end
before_filter :force_utf8_params
end
# Serialized columns in AR don't support UTF-8 well, so set the encoding on those as well.
class ActiveRecord::Base
def unserialize_attribute_with_utf8(attr_name)
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
value = unserialize_attribute_without_utf8(attr_name)
traverse.call(value, force_encoding)
end
alias_method_chain :unserialize_attribute, :utf8
end
# Make sure the flash sets the encoding to UTF-8 as well.
module ActionController
module Flash
class FlashHash
def [](k)
v = super
v.is_a?(String) ? v.force_encoding("UTF-8") : v
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment