Remove NilClass whiners feature.

Removing this feature causes boost in performance when using Ruby 1.9.

Ruby 1.9 started to do implicit conversions using `to_ary` and `to_str`
in some STDLIB methods (like Array#join). To do such implicit conversions,
Ruby 1.9 always dispatches the method and rescues the NoMethodError exception
in case one is raised.

Therefore, since the whiners feature defined NilClass#method_missing, such
implicit conversions for nil became much, much slower. In fact, just defining
NilClass#method_missing (even without the whiners feature) already causes a
massive slow down. Here is a snippet that shows such slow down:

    require "benchmark"
    Benchmark.realtime { 1_000.times { [nil,nil,nil].join } }

    class NilClass
      def method_missing(*args)
        raise NoMethodError
      end
    end

    Benchmark.realtime { 1_000.times { [nil,nil,nil].join } }
This commit is contained in:
José Valim 2011-12-08 20:21:48 +01:00
parent 7280787a53
commit d1abf29e79
3 changed files with 2 additions and 85 deletions

View File

@ -2222,8 +2222,6 @@ MSG
include AutosaveAssociation, NestedAttributes include AutosaveAssociation, NestedAttributes
include Aggregations, Transactions, Reflection, Serialization, Store include Aggregations, Transactions, Reflection, Serialization, Store
NilClass.add_whiner(self) if NilClass.respond_to?(:add_whiner)
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
# (Alias for the protected read_attribute method). # (Alias for the protected read_attribute method).

View File

@ -1,21 +1,6 @@
# Extensions to +nil+ which allow for more helpful error messages for people who # Extensions to +nil+ which allow for more helpful error messages for people who
# are new to Rails. # are new to Rails.
# #
# Ruby raises NoMethodError if you invoke a method on an object that does not
# respond to it:
#
# $ ruby -e nil.destroy
# -e:1: undefined method `destroy' for nil:NilClass (NoMethodError)
#
# With these extensions, if the method belongs to the public interface of the
# classes in NilClass::WHINERS the error message suggests which could be the
# actual intended class:
#
# $ rails runner nil.destroy
# ...
# You might have expected an instance of ActiveRecord::Base.
# ...
#
# NilClass#id exists in Ruby 1.8 (though it is deprecated). Since +id+ is a fundamental # NilClass#id exists in Ruby 1.8 (though it is deprecated). Since +id+ is a fundamental
# method of Active Record models NilClass#id is redefined as well to raise a RuntimeError # method of Active Record models NilClass#id is redefined as well to raise a RuntimeError
# and warn the user. She probably wanted a model database identifier and the 4 # and warn the user. She probably wanted a model database identifier and the 4
@ -25,36 +10,13 @@
# By default it is on in development and test modes, and it is off in production # By default it is on in development and test modes, and it is off in production
# mode. # mode.
class NilClass class NilClass
METHOD_CLASS_MAP = Hash.new
def self.add_whiner(klass) def self.add_whiner(klass)
methods = klass.public_instance_methods - public_instance_methods ActiveSupport::Deprecation.warn "NilClass.add_whiner is deprecated and this functionality is " \
class_name = klass.name "removed from Rails versions as it affects Ruby 1.9 performance.", caller
methods.each { |method| METHOD_CLASS_MAP[method.to_sym] = class_name }
end end
add_whiner ::Array
# Raises a RuntimeError when you attempt to call +id+ on +nil+. # Raises a RuntimeError when you attempt to call +id+ on +nil+.
def id def id
raise RuntimeError, "Called id for nil, which would mistakenly be #{object_id} -- if you really wanted the id of nil, use object_id", caller raise RuntimeError, "Called id for nil, which would mistakenly be #{object_id} -- if you really wanted the id of nil, use object_id", caller
end end
private
def method_missing(method, *args)
if klass = METHOD_CLASS_MAP[method]
raise_nil_warning_for klass, method, caller
else
super
end
end
# Raises a NoMethodError when you attempt to call a method on +nil+.
def raise_nil_warning_for(class_name = nil, selector = nil, with_caller = nil)
message = "You have a nil object when you didn't expect it!"
message << "\nYou might have expected an instance of #{class_name}." if class_name
message << "\nThe error occurred while evaluating nil.#{selector}" if selector
raise NoMethodError, message, with_caller || caller
end
end end

View File

@ -1,54 +1,11 @@
# Stub to enable testing without Active Record
module ActiveRecord
class Base
def save!
end
end
end
require 'abstract_unit' require 'abstract_unit'
require 'active_support/whiny_nil' require 'active_support/whiny_nil'
NilClass.add_whiner ::ActiveRecord::Base
class WhinyNilTest < Test::Unit::TestCase class WhinyNilTest < Test::Unit::TestCase
def test_unchanged
nil.method_thats_not_in_whiners
rescue NoMethodError => nme
assert_match(/nil:NilClass/, nme.message)
end
def test_active_record
nil.save!
rescue NoMethodError => nme
assert_no_match(/nil:NilClass/, nme.message)
assert_match(/nil\.save!/, nme.message)
end
def test_array
nil.each
rescue NoMethodError => nme
assert_no_match(/nil:NilClass/, nme.message)
assert_match(/nil\.each/, nme.message)
end
def test_id def test_id
nil.id nil.id
rescue RuntimeError => nme rescue RuntimeError => nme
assert_no_match(/nil:NilClass/, nme.message) assert_no_match(/nil:NilClass/, nme.message)
assert_match(Regexp.new(nil.object_id.to_s), nme.message) assert_match(Regexp.new(nil.object_id.to_s), nme.message)
end end
def test_no_to_ary_coercion
nil.to_ary
rescue NoMethodError => nme
assert_no_match(/nil:NilClass/, nme.message)
assert_match(/nil\.to_ary/, nme.message)
end
def test_no_to_str_coercion
nil.to_str
rescue NoMethodError => nme
assert_match(/nil:NilClass/, nme.message)
end
end end