Compare commits

..

6 Commits

Author SHA1 Message Date
Charlie Somerville
24e5712294 Merge pull request #26 from github/kill-whiny-nils
Kill whiny nils
2013-10-29 20:32:13 -07:00
Charlie Somerville
8f6bafc333 💀 whiny nils 2013-10-29 20:25:48 -07:00
Charlie Somerville
c717a84b5d Merge pull request #24 from github/avoid-extension-when-instantiating-extended-association
Avoid extension when instantiating extended association
2013-10-29 20:23:28 -07:00
Charlie Somerville
d537304b20 replace :: with _ to avoid wrong constant name exceptions 2013-10-29 20:16:52 -07:00
Charlie Somerville
ca90ecf2cb use terrible hacks to make this work when rails tries to marshal 2013-10-29 20:06:11 -07:00
Charlie Somerville
4bb1d3ef20 cache a class with the extend module pre-included 2013-10-29 20:06:11 -07:00
7 changed files with 19 additions and 139 deletions

View File

@@ -47,14 +47,29 @@ module ActiveRecord
# instantiation of the actual post records.
class AssociationProxy #:nodoc:
alias_method :proxy_respond_to?, :respond_to?
alias_method :proxy_extend, :extend
delegate :to_param, :to => :proxy_target
instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id)$|^__|^respond_to_missing|proxy_/ }
def self.new(owner, reflection)
klass =
reflection.cached_extend_class ||=
if reflection.options[:extend]
const_name = "AR_CACHED_EXTEND_CLASS_#{reflection.name}_#{reflection.options[:extend].join("_").gsub("::","_")}"
reflection.active_record.const_set(const_name, Class.new(self) do
include *reflection.options[:extend]
end)
else
self
end
proxy = klass.allocate
proxy.send(:initialize, owner, reflection)
proxy
end
def initialize(owner, reflection)
@owner, @reflection = owner, reflection
reflection.check_validity!
Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
reset
end

View File

@@ -77,6 +77,7 @@ module ActiveRecord
# those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
class MacroReflection
attr_reader :active_record
attr_accessor :cached_extend_class
def initialize(macro, name, options, active_record)
@macro, @name, @options, @active_record = macro, name, options, active_record

View File

@@ -1,64 +0,0 @@
# Extensions to +nil+ which allow for more helpful error messages for people who
# 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:
#
# $ script/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
# 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
# returned by the original method could result in obscure bugs.
#
# The flag <tt>config.whiny_nils</tt> determines whether this feature is enabled.
# By default it is on in development and test modes, and it is off in production
# mode.
class NilClass
WHINERS = [::Array]
WHINERS << ::ActiveRecord::Base if defined? ::ActiveRecord
METHOD_CLASS_MAP = Hash.new
WHINERS.each do |klass|
methods = klass.public_instance_methods - public_instance_methods
class_name = klass.name
methods.each { |method| METHOD_CLASS_MAP[method.to_sym] = class_name }
end
# Raises a RuntimeError when you attempt to call +id+ on +nil+.
def id
raise RuntimeError, "Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id", caller
end
private
def method_missing(method, *args, &block)
# Ruby 1.9.2: disallow explicit coercion via method_missing.
if method == :to_ary || method == :to_str
super
elsif 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

View File

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

View File

@@ -5,9 +5,6 @@
# since you don't have to restart the webserver when you make code changes.
config.cache_classes = false
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Show full error reports and disable caching
config.action_controller.consider_all_requests_local = true
config.action_controller.perform_caching = false

View File

@@ -6,9 +6,6 @@
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Show full error reports and disable caching
config.action_controller.consider_all_requests_local = true
config.action_controller.perform_caching = false
@@ -25,4 +22,4 @@ config.action_mailer.delivery_method = :test
# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql
# config.active_record.schema_format = :sql

View File

@@ -147,7 +147,6 @@ module Rails
initialize_framework_logging
initialize_dependency_mechanism
initialize_whiny_nils
initialize_time_zone
initialize_i18n
@@ -543,12 +542,6 @@ Run `rake gems:install` to install the missing gems.
ActiveSupport::Dependencies.mechanism = configuration.cache_classes ? :require : :load
end
# Loads support for "whiny nil" (noisy warnings when methods are invoked
# on +nil+ values) if Configuration#whiny_nils is true.
def initialize_whiny_nils
require('active_support/whiny_nil') if configuration.whiny_nils
end
# Sets the default value for Time.zone, and turns on ActiveRecord::Base#time_zone_aware_attributes.
# If assigned value cannot be matched to a TimeZone, an exception will be raised.
def initialize_time_zone
@@ -752,10 +745,6 @@ Run `rake gems:install` to install the missing gems.
# The root of the application's views. (Defaults to <tt>app/views</tt>.)
attr_accessor :view_path
# Set to +true+ if you want to be warned (noisily) when you try to invoke
# any method of +nil+. Set to +false+ for the standard Ruby behavior.
attr_accessor :whiny_nils
# The list of plugins to load. If this is set to <tt>nil</tt>, all plugins will
# be loaded. If this is set to <tt>[]</tt>, no plugins will be loaded. Otherwise,
# plugins will be loaded in the order specified.
@@ -870,7 +859,6 @@ Run `rake gems:install` to install the missing gems.
self.preload_frameworks = default_preload_frameworks
self.cache_classes = default_cache_classes
self.dependency_loading = default_dependency_loading
self.whiny_nils = default_whiny_nils
self.plugins = default_plugins
self.plugin_paths = default_plugin_paths
self.plugin_locators = default_plugin_locators
@@ -1067,10 +1055,6 @@ Run `rake gems:install` to install the missing gems.
true
end
def default_whiny_nils
false
end
def default_plugins
nil
end