mirror of
https://github.com/github/rails.git
synced 2026-01-13 00:28:26 -05:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24e5712294 | ||
|
|
8f6bafc333 | ||
|
|
c717a84b5d | ||
|
|
d537304b20 | ||
|
|
ca90ecf2cb | ||
|
|
4bb1d3ef20 | ||
|
|
3b7754c950 | ||
|
|
75638c576b |
@@ -47,14 +47,29 @@ module ActiveRecord
|
|||||||
# instantiation of the actual post records.
|
# instantiation of the actual post records.
|
||||||
class AssociationProxy #:nodoc:
|
class AssociationProxy #:nodoc:
|
||||||
alias_method :proxy_respond_to?, :respond_to?
|
alias_method :proxy_respond_to?, :respond_to?
|
||||||
alias_method :proxy_extend, :extend
|
|
||||||
delegate :to_param, :to => :proxy_target
|
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_/ }
|
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)
|
def initialize(owner, reflection)
|
||||||
@owner, @reflection = owner, reflection
|
@owner, @reflection = owner, reflection
|
||||||
reflection.check_validity!
|
reflection.check_validity!
|
||||||
Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
|
|
||||||
reset
|
reset
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ module ActiveRecord
|
|||||||
# those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
# those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
||||||
class MacroReflection
|
class MacroReflection
|
||||||
attr_reader :active_record
|
attr_reader :active_record
|
||||||
|
attr_accessor :cached_extend_class
|
||||||
|
|
||||||
def initialize(macro, name, options, active_record)
|
def initialize(macro, name, options, active_record)
|
||||||
@macro, @name, @options, @active_record = macro, name, options, active_record
|
@macro, @name, @options, @active_record = macro, name, options, active_record
|
||||||
|
|||||||
134
activesupport/lib/active_support/concern.rb
Normal file
134
activesupport/lib/active_support/concern.rb
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
module ActiveSupport
|
||||||
|
# A typical module looks like this:
|
||||||
|
#
|
||||||
|
# module M
|
||||||
|
# def self.included(base)
|
||||||
|
# base.extend ClassMethods
|
||||||
|
# base.class_eval do
|
||||||
|
# scope :disabled, -> { where(disabled: true) }
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# module ClassMethods
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# By using <tt>ActiveSupport::Concern</tt> the above module could instead be
|
||||||
|
# written as:
|
||||||
|
#
|
||||||
|
# require 'active_support/concern'
|
||||||
|
#
|
||||||
|
# module M
|
||||||
|
# extend ActiveSupport::Concern
|
||||||
|
#
|
||||||
|
# included do
|
||||||
|
# scope :disabled, -> { where(disabled: true) }
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# module ClassMethods
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Moreover, it gracefully handles module dependencies. Given a +Foo+ module
|
||||||
|
# and a +Bar+ module which depends on the former, we would typically write the
|
||||||
|
# following:
|
||||||
|
#
|
||||||
|
# module Foo
|
||||||
|
# def self.included(base)
|
||||||
|
# base.class_eval do
|
||||||
|
# def self.method_injected_by_foo
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# module Bar
|
||||||
|
# def self.included(base)
|
||||||
|
# base.method_injected_by_foo
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# class Host
|
||||||
|
# include Foo # We need to include this dependency for Bar
|
||||||
|
# include Bar # Bar is the module that Host really needs
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
|
||||||
|
# could try to hide these from +Host+ directly including +Foo+ in +Bar+:
|
||||||
|
#
|
||||||
|
# module Bar
|
||||||
|
# include Foo
|
||||||
|
# def self.included(base)
|
||||||
|
# base.method_injected_by_foo
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# class Host
|
||||||
|
# include Bar
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
|
||||||
|
# is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
|
||||||
|
# module dependencies are properly resolved:
|
||||||
|
#
|
||||||
|
# require 'active_support/concern'
|
||||||
|
#
|
||||||
|
# module Foo
|
||||||
|
# extend ActiveSupport::Concern
|
||||||
|
# included do
|
||||||
|
# def self.method_injected_by_foo
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# module Bar
|
||||||
|
# extend ActiveSupport::Concern
|
||||||
|
# include Foo
|
||||||
|
#
|
||||||
|
# included do
|
||||||
|
# self.method_injected_by_foo
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# class Host
|
||||||
|
# include Bar # works, Bar takes care now of its dependencies
|
||||||
|
# end
|
||||||
|
module Concern
|
||||||
|
class MultipleIncludedBlocks < StandardError #:nodoc:
|
||||||
|
def initialize
|
||||||
|
super "Cannot define multiple 'included' blocks for a Concern"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.extended(base) #:nodoc:
|
||||||
|
base.instance_variable_set(:@_dependencies, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def append_features(base)
|
||||||
|
if base.instance_variable_defined?(:@_dependencies)
|
||||||
|
base.instance_variable_get(:@_dependencies) << self
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
return false if base < self
|
||||||
|
@_dependencies.each { |dep| base.send(:include, dep) }
|
||||||
|
super
|
||||||
|
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
|
||||||
|
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def included(base = nil, &block)
|
||||||
|
if base.nil?
|
||||||
|
raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block)
|
||||||
|
|
||||||
|
@_included_block = block
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -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
|
|
||||||
98
activesupport/test/concern_test.rb
Normal file
98
activesupport/test/concern_test.rb
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
require 'abstract_unit'
|
||||||
|
require 'active_support/concern'
|
||||||
|
|
||||||
|
class ConcernTest < ActiveSupport::TestCase
|
||||||
|
module Baz
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def baz
|
||||||
|
"baz"
|
||||||
|
end
|
||||||
|
|
||||||
|
def included_ran=(value)
|
||||||
|
@@included_ran = value
|
||||||
|
end
|
||||||
|
|
||||||
|
def included_ran
|
||||||
|
@@included_ran
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
included do
|
||||||
|
self.included_ran = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def baz
|
||||||
|
"baz"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Bar
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
include Baz
|
||||||
|
|
||||||
|
def bar
|
||||||
|
"bar"
|
||||||
|
end
|
||||||
|
|
||||||
|
def baz
|
||||||
|
"bar+" + super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Foo
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
include Bar, Baz
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@klass = Class.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_module_is_included_normally
|
||||||
|
@klass.send(:include, Baz)
|
||||||
|
assert_equal "baz", @klass.new.baz
|
||||||
|
assert @klass.included_modules.include?(ConcernTest::Baz)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_class_methods_are_extended
|
||||||
|
@klass.send(:include, Baz)
|
||||||
|
assert_equal "baz", @klass.baz
|
||||||
|
assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_included_block_is_ran
|
||||||
|
@klass.send(:include, Baz)
|
||||||
|
assert_equal true, @klass.included_ran
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_modules_dependencies_are_met
|
||||||
|
@klass.send(:include, Bar)
|
||||||
|
assert_equal "bar", @klass.new.bar
|
||||||
|
assert_equal "bar+baz", @klass.new.baz
|
||||||
|
assert_equal "baz", @klass.baz
|
||||||
|
assert @klass.included_modules.include?(ConcernTest::Bar)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_dependencies_with_multiple_modules
|
||||||
|
@klass.send(:include, Foo)
|
||||||
|
assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_raise_on_multiple_included_calls
|
||||||
|
assert_raises(ActiveSupport::Concern::MultipleIncludedBlocks) do
|
||||||
|
Module.new do
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
end
|
||||||
|
|
||||||
|
included do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -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
|
|
||||||
@@ -5,9 +5,6 @@
|
|||||||
# since you don't have to restart the webserver when you make code changes.
|
# since you don't have to restart the webserver when you make code changes.
|
||||||
config.cache_classes = false
|
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
|
# Show full error reports and disable caching
|
||||||
config.action_controller.consider_all_requests_local = true
|
config.action_controller.consider_all_requests_local = true
|
||||||
config.action_controller.perform_caching = false
|
config.action_controller.perform_caching = false
|
||||||
|
|||||||
@@ -6,9 +6,6 @@
|
|||||||
# and recreated between test runs. Don't rely on the data there!
|
# and recreated between test runs. Don't rely on the data there!
|
||||||
config.cache_classes = true
|
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
|
# Show full error reports and disable caching
|
||||||
config.action_controller.consider_all_requests_local = true
|
config.action_controller.consider_all_requests_local = true
|
||||||
config.action_controller.perform_caching = false
|
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.
|
# 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,
|
# 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
|
# like if you have constraints or database-specific column types
|
||||||
# config.active_record.schema_format = :sql
|
# config.active_record.schema_format = :sql
|
||||||
|
|||||||
@@ -147,7 +147,6 @@ module Rails
|
|||||||
initialize_framework_logging
|
initialize_framework_logging
|
||||||
|
|
||||||
initialize_dependency_mechanism
|
initialize_dependency_mechanism
|
||||||
initialize_whiny_nils
|
|
||||||
|
|
||||||
initialize_time_zone
|
initialize_time_zone
|
||||||
initialize_i18n
|
initialize_i18n
|
||||||
@@ -543,12 +542,6 @@ Run `rake gems:install` to install the missing gems.
|
|||||||
ActiveSupport::Dependencies.mechanism = configuration.cache_classes ? :require : :load
|
ActiveSupport::Dependencies.mechanism = configuration.cache_classes ? :require : :load
|
||||||
end
|
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.
|
# 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.
|
# If assigned value cannot be matched to a TimeZone, an exception will be raised.
|
||||||
def initialize_time_zone
|
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>.)
|
# The root of the application's views. (Defaults to <tt>app/views</tt>.)
|
||||||
attr_accessor :view_path
|
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
|
# 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,
|
# be loaded. If this is set to <tt>[]</tt>, no plugins will be loaded. Otherwise,
|
||||||
# plugins will be loaded in the order specified.
|
# 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.preload_frameworks = default_preload_frameworks
|
||||||
self.cache_classes = default_cache_classes
|
self.cache_classes = default_cache_classes
|
||||||
self.dependency_loading = default_dependency_loading
|
self.dependency_loading = default_dependency_loading
|
||||||
self.whiny_nils = default_whiny_nils
|
|
||||||
self.plugins = default_plugins
|
self.plugins = default_plugins
|
||||||
self.plugin_paths = default_plugin_paths
|
self.plugin_paths = default_plugin_paths
|
||||||
self.plugin_locators = default_plugin_locators
|
self.plugin_locators = default_plugin_locators
|
||||||
@@ -1067,10 +1055,6 @@ Run `rake gems:install` to install the missing gems.
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_whiny_nils
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_plugins
|
def default_plugins
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user