mirror of https://github.com/rails/rails
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:
parent
7280787a53
commit
d1abf29e79
|
@ -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).
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue