aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/core_ext/module
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support/core_ext/module')
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb53
-rw-r--r--activesupport/lib/active_support/core_ext/module/anonymous.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/module/attr_internal.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb80
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb144
-rw-r--r--activesupport/lib/active_support/core_ext/module/concerning.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb148
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb62
-rw-r--r--activesupport/lib/active_support/core_ext/module/method_transplanting.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/module/qualified_const.rb52
-rw-r--r--activesupport/lib/active_support/core_ext/module/reachable.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/module/redefine_method.rb40
-rw-r--r--activesupport/lib/active_support/core_ext/module/remove_method.rb12
14 files changed, 427 insertions, 215 deletions
diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb
index b6934b9c54..6f64d11627 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -1,52 +1,6 @@
-class Module
- # NOTE: This method is deprecated. Please use <tt>Module#prepend</tt> that
- # comes with Ruby 2.0 or newer instead.
- #
- # Encapsulates the common pattern of:
- #
- # alias_method :foo_without_feature, :foo
- # alias_method :foo, :foo_with_feature
- #
- # With this, you simply do:
- #
- # alias_method_chain :foo, :feature
- #
- # And both aliases are set up for you.
- #
- # Query and bang methods (foo?, foo!) keep the same punctuation:
- #
- # alias_method_chain :foo?, :feature
- #
- # is equivalent to
- #
- # alias_method :foo_without_feature?, :foo?
- # alias_method :foo?, :foo_with_feature?
- #
- # so you can safely chain foo, foo?, foo! and/or foo= with the same feature.
- def alias_method_chain(target, feature)
- ActiveSupport::Deprecation.warn("alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super.")
-
- # Strip out punctuation on predicates, bang or writer methods since
- # e.g. target?_without_feature is not a valid method name.
- aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
- yield(aliased_target, punctuation) if block_given?
-
- with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
- without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
-
- alias_method without_method, target
- alias_method target, with_method
-
- case
- when public_method_defined?(without_method)
- public target
- when protected_method_defined?(without_method)
- protected target
- when private_method_defined?(without_method)
- private target
- end
- end
+# frozen_string_literal: true
+class Module
# Allows you to make aliases for attributes, which includes
# getter, setter, and a predicate.
#
@@ -65,6 +19,9 @@ class Module
# e.subject = "Megastars"
# e.title # => "Megastars"
def alias_attribute(new_name, old_name)
+ # The following reader methods use an explicit `self` receiver in order to
+ # support aliases that start with an uppercase letter. Otherwise, they would
+ # be resolved as constants instead.
module_eval <<-STR, __FILE__, __LINE__ + 1
def #{new_name}; self.#{old_name}; end # def subject; self.title; end
def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end
diff --git a/activesupport/lib/active_support/core_ext/module/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb
index 0ecc67a855..d1c86b8722 100644
--- a/activesupport/lib/active_support/core_ext/module/anonymous.rb
+++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Module
# A module may or may not have a name.
#
@@ -7,7 +9,7 @@ class Module
# m = Module.new
# m.name # => nil
#
- # +anonymous?+ method returns true if module does not have a name:
+ # +anonymous?+ method returns true if module does not have a name, false otherwise:
#
# Module.new.anonymous? # => true
#
@@ -18,8 +20,10 @@ class Module
# via the +module+ or +class+ keyword or by an explicit assignment:
#
# m = Module.new # creates an anonymous module
- # M = m # => m gets a name here as a side-effect
+ # m.anonymous? # => true
+ # M = m # m gets a name here as a side-effect
# m.name # => "M"
+ # m.anonymous? # => false
def anonymous?
name.nil?
end
diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
index 93fb598650..7801f6d181 100644
--- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb
+++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
@@ -1,12 +1,14 @@
+# frozen_string_literal: true
+
class Module
# Declares an attribute reader backed by an internally-named instance variable.
def attr_internal_reader(*attrs)
- attrs.each {|attr_name| attr_internal_define(attr_name, :reader)}
+ attrs.each { |attr_name| attr_internal_define(attr_name, :reader) }
end
# Declares an attribute writer backed by an internally-named instance variable.
def attr_internal_writer(*attrs)
- attrs.each {|attr_name| attr_internal_define(attr_name, :writer)}
+ attrs.each { |attr_name| attr_internal_define(attr_name, :writer) }
end
# Declares an attribute reader and writer backed by an internally-named instance
@@ -18,7 +20,7 @@ class Module
alias_method :attr_internal, :attr_internal_accessor
class << self; attr_accessor :attr_internal_naming_format end
- self.attr_internal_naming_format = '@_%s'
+ self.attr_internal_naming_format = "@_%s"
private
def attr_internal_ivar_name(attr)
@@ -26,7 +28,7 @@ class Module
end
def attr_internal_define(attr_name, type)
- internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '')
+ internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, "")
# use native attr_* methods as they are faster on some Ruby implementations
send("attr_#{type}", internal_name)
attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index d4e6b5a1ac..5850e0193f 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -1,12 +1,13 @@
-require 'active_support/core_ext/array/extract_options'
+# frozen_string_literal: true
# Extends the module object with class/module and instance accessors for
# class/module attributes, just like the native attr* accessors for instance
# attributes.
class Module
# Defines a class attribute and creates a class and instance reader methods.
- # The underlying the class variable is set to +nil+, if it is not previously
- # defined.
+ # The underlying class variable is set to +nil+, if it is not previously
+ # defined. All class and instance methods created will be public, even if
+ # this method is called with a private or protected access modifier.
#
# module HairColors
# mattr_reader :hair_colors
@@ -19,15 +20,15 @@ class Module
# The attribute name must be a valid method name in Ruby.
#
# module Foo
- # mattr_reader :"1_Badname "
+ # mattr_reader :"1_Badname"
# end
- # # => NameError: invalid attribute name
+ # # => NameError: invalid attribute name: 1_Badname
#
# If you want to opt out the creation on the instance reader method, pass
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
#
# module HairColors
- # mattr_writer :hair_colors, instance_reader: false
+ # mattr_reader :hair_colors, instance_reader: false
# end
#
# class Person
@@ -36,24 +37,20 @@ class Module
#
# Person.new.hair_colors # => NoMethodError
#
- #
- # Also, you can pass a block to set up the attribute with a default value.
+ # You can set a default value for the attribute.
#
# module HairColors
- # cattr_reader :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
+ # mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red]
# end
#
# class Person
# include HairColors
# end
#
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
- def mattr_reader(*syms)
- options = syms.extract_options!
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
+ def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil)
syms.each do |sym|
- raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@#{sym} = nil unless defined? @@#{sym}
@@ -62,20 +59,24 @@ class Module
end
EOS
- unless options[:instance_reader] == false || options[:instance_accessor] == false
+ if instance_reader && instance_accessor
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}
@@#{sym}
end
EOS
end
- class_variable_set("@@#{sym}", yield) if block_given?
+
+ sym_default_value = (block_given? && default.nil?) ? yield : default
+ class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil?
end
end
alias :cattr_reader :mattr_reader
# Defines a class attribute and creates a class and instance writer methods to
- # allow assignment to the attribute.
+ # allow assignment to the attribute. All class and instance methods created
+ # will be public, even if this method is called with a private or protected
+ # access modifier.
#
# module HairColors
# mattr_writer :hair_colors
@@ -103,12 +104,10 @@ class Module
#
# Person.new.hair_colors = [:blonde, :red] # => NoMethodError
#
- # Also, you can pass a block to set up the attribute with a default value.
+ # You can set a default value for the attribute.
#
- # class HairColors
- # mattr_writer :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
+ # module HairColors
+ # mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red]
# end
#
# class Person
@@ -116,10 +115,9 @@ class Module
# end
#
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
- def mattr_writer(*syms)
- options = syms.extract_options!
+ def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil)
syms.each do |sym|
- raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@#{sym} = nil unless defined? @@#{sym}
@@ -128,19 +126,23 @@ class Module
end
EOS
- unless options[:instance_writer] == false || options[:instance_accessor] == false
+ if instance_writer && instance_accessor
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}=(obj)
@@#{sym} = obj
end
EOS
end
- send("#{sym}=", yield) if block_given?
+
+ sym_default_value = (block_given? && default.nil?) ? yield : default
+ send("#{sym}=", sym_default_value) unless sym_default_value.nil?
end
end
alias :cattr_writer :mattr_writer
# Defines both class and instance accessors for class attributes.
+ # All class and instance methods created will be public, even if
+ # this method is called with a private or protected access modifier.
#
# module HairColors
# mattr_accessor :hair_colors
@@ -150,19 +152,19 @@ class Module
# include HairColors
# end
#
- # Person.hair_colors = [:brown, :black, :blonde, :red]
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
+ # HairColors.hair_colors = [:brown, :black, :blonde, :red]
+ # HairColors.hair_colors # => [:brown, :black, :blonde, :red]
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
#
# If a subclass changes the value then that would also change the value for
# parent class. Similarly if parent class changes the value then that would
# change the value of subclasses too.
#
- # class Male < Person
+ # class Citizen < Person
# end
#
- # Male.hair_colors << :blue
- # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
+ # Citizen.new.hair_colors << :blue
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue]
#
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
@@ -191,12 +193,10 @@ class Module
# Person.new.hair_colors = [:brown] # => NoMethodError
# Person.new.hair_colors # => NoMethodError
#
- # Also you can pass a block to set up the attribute with a default value.
+ # You can set a default value for the attribute.
#
# module HairColors
- # mattr_accessor :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
+ # mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red]
# end
#
# class Person
@@ -204,9 +204,9 @@ class Module
# end
#
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
- def mattr_accessor(*syms, &blk)
- mattr_reader(*syms, &blk)
- mattr_writer(*syms, &blk)
+ def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
+ mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, &blk)
+ mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default)
end
alias :cattr_accessor :mattr_accessor
end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
new file mode 100644
index 0000000000..cb996e9e6d
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+# Extends the module object with class/module and instance accessors for
+# class/module attributes, just like the native attr* accessors for instance
+# attributes, but does so on a per-thread basis.
+#
+# So the values are scoped within the Thread.current space under the class name
+# of the module.
+class Module
+ # Defines a per-thread class attribute and creates class and instance reader methods.
+ # The underlying per-thread class variable is set to +nil+, if it is not previously defined.
+ #
+ # module Current
+ # thread_mattr_reader :user
+ # end
+ #
+ # Current.user # => nil
+ # Thread.current[:attr_Current_user] = "DHH"
+ # Current.user # => "DHH"
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # module Foo
+ # thread_mattr_reader :"1_Badname"
+ # end
+ # # => NameError: invalid attribute name: 1_Badname
+ #
+ # If you want to opt out of the creation of the instance reader method, pass
+ # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # class Current
+ # thread_mattr_reader :user, instance_reader: false
+ # end
+ #
+ # Current.new.user # => NoMethodError
+ def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true) # :nodoc:
+ syms.each do |sym|
+ raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
+
+ # The following generated method concatenates `name` because we want it
+ # to work with inheritance via polymorphism.
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def self.#{sym}
+ Thread.current["attr_" + name + "_#{sym}"]
+ end
+ EOS
+
+ if instance_reader && instance_accessor
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{sym}
+ self.class.#{sym}
+ end
+ EOS
+ end
+ end
+ end
+ alias :thread_cattr_reader :thread_mattr_reader
+
+ # Defines a per-thread class attribute and creates a class and instance writer methods to
+ # allow assignment to the attribute.
+ #
+ # module Current
+ # thread_mattr_writer :user
+ # end
+ #
+ # Current.user = "DHH"
+ # Thread.current[:attr_Current_user] # => "DHH"
+ #
+ # If you want to opt out of the creation of the instance writer method, pass
+ # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # class Current
+ # thread_mattr_writer :user, instance_writer: false
+ # end
+ #
+ # Current.new.user = "DHH" # => NoMethodError
+ def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true) # :nodoc:
+ syms.each do |sym|
+ raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
+
+ # The following generated method concatenates `name` because we want it
+ # to work with inheritance via polymorphism.
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def self.#{sym}=(obj)
+ Thread.current["attr_" + name + "_#{sym}"] = obj
+ end
+ EOS
+
+ if instance_writer && instance_accessor
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{sym}=(obj)
+ self.class.#{sym} = obj
+ end
+ EOS
+ end
+ end
+ end
+ alias :thread_cattr_writer :thread_mattr_writer
+
+ # Defines both class and instance accessors for class attributes.
+ #
+ # class Account
+ # thread_mattr_accessor :user
+ # end
+ #
+ # Account.user = "DHH"
+ # Account.user # => "DHH"
+ # Account.new.user # => "DHH"
+ #
+ # If a subclass changes the value, the parent class' value is not changed.
+ # Similarly, if the parent class changes the value, the value of subclasses
+ # is not changed.
+ #
+ # class Customer < Account
+ # end
+ #
+ # Customer.user = "Rafael"
+ # Customer.user # => "Rafael"
+ # Account.user # => "DHH"
+ #
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ #
+ # class Current
+ # thread_mattr_accessor :user, instance_writer: false, instance_reader: false
+ # end
+ #
+ # Current.new.user = "DHH" # => NoMethodError
+ # Current.new.user # => NoMethodError
+ #
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ #
+ # class Current
+ # thread_mattr_accessor :user, instance_accessor: false
+ # end
+ #
+ # Current.new.user = "DHH" # => NoMethodError
+ # Current.new.user # => NoMethodError
+ def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true)
+ thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor)
+ thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor)
+ end
+ alias :thread_cattr_accessor :thread_mattr_accessor
+end
diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
index 65b88b9bbd..7bbbf321ab 100644
--- a/activesupport/lib/active_support/core_ext/module/concerning.rb
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -1,4 +1,6 @@
-require 'active_support/concern'
+# frozen_string_literal: true
+
+require "active_support/concern"
class Module
# = Bite-sized separation of concerns
@@ -20,7 +22,7 @@ class Module
#
# == Using comments:
#
- # class Todo
+ # class Todo < ApplicationRecord
# # Other todo implementation
# # ...
#
@@ -28,7 +30,6 @@ class Module
# has_many :events
#
# before_create :track_creation
- # after_destroy :track_deletion
#
# private
# def track_creation
@@ -40,7 +41,7 @@ class Module
#
# Noisy syntax.
#
- # class Todo
+ # class Todo < ApplicationRecord
# # Other todo implementation
# # ...
#
@@ -50,7 +51,6 @@ class Module
# included do
# has_many :events
# before_create :track_creation
- # after_destroy :track_deletion
# end
#
# private
@@ -68,7 +68,7 @@ class Module
# increased overhead can be a reasonable tradeoff even if it reduces our
# at-a-glance perception of how things work.
#
- # class Todo
+ # class Todo < ApplicationRecord
# # Other todo implementation
# # ...
#
@@ -80,7 +80,7 @@ class Module
# By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
# separate bite-sized concerns.
#
- # class Todo
+ # class Todo < ApplicationRecord
# # Other todo implementation
# # ...
#
@@ -88,7 +88,6 @@ class Module
# included do
# has_many :events
# before_create :track_creation
- # after_destroy :track_deletion
# end
#
# private
@@ -99,7 +98,7 @@ class Module
# end
#
# Todo.ancestors
- # # => [Todo, Todo::EventTracking, Object]
+ # # => [Todo, Todo::EventTracking, ApplicationRecord, Object]
#
# This small step has some wonderful ripple effects. We can
# * grok the behavior of our class in one glance,
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 9b7a429db9..3128539112 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -1,14 +1,18 @@
-require 'set'
+# frozen_string_literal: true
+
+require "set"
class Module
# Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
# option is not used.
class DelegationError < NoMethodError; end
- RUBY_RESERVED_WORDS = Set.new(
- %w(alias and BEGIN begin break case class def defined? do else elsif END
- end ensure false for if in module next nil not or redo rescue retry
- return self super then true undef unless until when while yield)
+ RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do
+ else elsif END end ensure false for if in module next nil not or redo rescue retry
+ return self super then true undef unless until when while yield)
+ DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
+ DELEGATION_RESERVED_METHOD_NAMES = Set.new(
+ RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
).freeze
# Provides a +delegate+ class method to easily expose contained objects'
@@ -17,7 +21,9 @@ class Module
# ==== Options
# * <tt>:to</tt> - Specifies the target object
# * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
- # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ from being raised
+ # * <tt>:allow_nil</tt> - If set to true, prevents a +Module::DelegationError+
+ # from being raised
+ # * <tt>:private</tt> - If set to true, changes method visibility to private
#
# The macro receives one or more method names (specified as symbols or
# strings) and the name of the target object via the <tt>:to</tt> option
@@ -108,19 +114,34 @@ class Module
# invoice.customer_name # => 'John Doe'
# invoice.customer_address # => 'Vimmersvej 13'
#
+ # The delegated methods are public by default.
+ # Pass <tt>private: true</tt> to change that.
+ #
+ # class User < ActiveRecord::Base
+ # has_one :profile
+ # delegate :first_name, to: :profile
+ # delegate :date_of_birth, to: :profile, private: true
+ #
+ # def age
+ # Date.today.year - date_of_birth.year
+ # end
+ # end
+ #
+ # User.new.first_name # => "Tomas"
+ # User.new.date_of_birth # => NoMethodError: private method `date_of_birth' called for #<User:0x00000008221340>
+ # User.new.age # => 2
+ #
# If the target is +nil+ and does not respond to the delegated method a
- # +NoMethodError+ is raised, as with any other value. Sometimes, however, it
- # makes sense to be robust to that situation and that is the purpose of the
- # <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and
- # responds to the method, everything works as usual. But if it is +nil+ and
- # does not respond to the delegated method, +nil+ is returned.
+ # +Module::DelegationError+ is raised. If you wish to instead return +nil+,
+ # use the <tt>:allow_nil</tt> option.
#
# class User < ActiveRecord::Base
# has_one :profile
# delegate :age, to: :profile
# end
#
- # User.new.age # raises NoMethodError: undefined method `age'
+ # User.new.age
+ # # => Module::DelegationError: User#age delegated to profile.age, but profile is nil
#
# But if not having a profile yet is fine and should not be an error
# condition:
@@ -147,36 +168,32 @@ class Module
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
#
# The target method must be public, otherwise it will raise +NoMethodError+.
- #
- def delegate(*methods)
- options = methods.pop
- unless options.is_a?(Hash) && to = options[:to]
- raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
+ def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil)
+ unless to
+ raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter)."
end
- prefix, allow_nil = options.values_at(:prefix, :allow_nil)
-
- if prefix == true && to =~ /^[^a-z_]/
- raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
+ if prefix == true && /^[^a-z_]/.match?(to)
+ raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
end
method_prefix = \
if prefix
"#{prefix == true ? to : prefix}_"
else
- ''
+ ""
end
- file, line = caller(1, 1).first.split(':', 2)
- line = line.to_i
+ location = caller_locations(1, 1).first
+ file, line = location.path, location.lineno
to = to.to_s
- to = "self.#{to}" if RUBY_RESERVED_WORDS.include?(to)
+ to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
- methods.each do |method|
+ method_names = methods.map do |method|
# Attribute writer methods only accept one argument. Makes sure []=
# methods still accept two arguments.
- definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
+ definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block"
# The following generated method calls the target exactly once, storing
# the returned value in a dummy variable.
@@ -193,7 +210,7 @@ class Module
" _.#{method}(#{definition})",
"end",
"end"
- ].join ';'
+ ].join ";"
else
exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
@@ -208,10 +225,83 @@ class Module
" raise",
" end",
"end"
- ].join ';'
+ ].join ";"
end
module_eval(method_def, file, line)
end
+
+ private(*method_names) if private
+ method_names
+ end
+
+ # When building decorators, a common pattern may emerge:
+ #
+ # class Partition
+ # def initialize(event)
+ # @event = event
+ # end
+ #
+ # def person
+ # @event.detail.person || @event.creator
+ # end
+ #
+ # private
+ # def respond_to_missing?(name, include_private = false)
+ # @event.respond_to?(name, include_private)
+ # end
+ #
+ # def method_missing(method, *args, &block)
+ # @event.send(method, *args, &block)
+ # end
+ # end
+ #
+ # With <tt>Module#delegate_missing_to</tt>, the above is condensed to:
+ #
+ # class Partition
+ # delegate_missing_to :@event
+ #
+ # def initialize(event)
+ # @event = event
+ # end
+ #
+ # def person
+ # @event.detail.person || @event.creator
+ # end
+ # end
+ #
+ # The target can be anything callable within the object, e.g. instance
+ # variables, methods, constants, etc.
+ #
+ # The delegated method must be public on the target, otherwise it will
+ # raise +NoMethodError+.
+ def delegate_missing_to(target)
+ target = target.to_s
+ target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
+
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def respond_to_missing?(name, include_private = false)
+ # It may look like an oversight, but we deliberately do not pass
+ # +include_private+, because they do not get delegated.
+
+ #{target}.respond_to?(name) || super
+ end
+
+ def method_missing(method, *args, &block)
+ if #{target}.respond_to?(method)
+ #{target}.public_send(method, *args, &block)
+ else
+ begin
+ super
+ rescue NoMethodError
+ if #{target}.nil?
+ raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
+ else
+ raise
+ end
+ end
+ end
+ end
+ RUBY
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb
index 56d670fbe8..71c42eb357 100644
--- a/activesupport/lib/active_support/core_ext/module/deprecation.rb
+++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Module
# deprecate :foo
# deprecate bar: 'message'
@@ -13,8 +15,8 @@ class Module
#
# class MyLib::Deprecator
# def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
- # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
- # Kernel.warn message
+ # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
+ # Kernel.warn message
# end
# end
def deprecate(*method_names)
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index f1d26ef28f..9b6df40596 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -1,17 +1,29 @@
-require 'active_support/inflector'
+# frozen_string_literal: true
+
+require "active_support/inflector"
class Module
# Returns the name of the module containing this one.
#
- # M::N.parent_name # => "M"
- def parent_name
- if defined? @parent_name
+ # M::N.module_parent_name # => "M"
+ def module_parent_name
+ if defined?(@parent_name)
@parent_name
else
- @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
+ parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
+ @parent_name = parent_name unless frozen?
+ parent_name
end
end
+ def parent_name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ `Module#parent_name` has been renamed to `module_parent_name`.
+ `parent_name` is deprecated and will be removed in Rails 6.1.
+ MSG
+ module_parent_name
+ end
+
# Returns the module which contains this one according to its name.
#
# module M
@@ -20,15 +32,23 @@ class Module
# end
# X = M::N
#
- # M::N.parent # => M
- # X.parent # => M
+ # M::N.module_parent # => M
+ # X.module_parent # => M
#
# The parent of top-level and anonymous modules is Object.
#
- # M.parent # => Object
- # Module.new.parent # => Object
+ # M.module_parent # => Object
+ # Module.new.module_parent # => Object
+ def module_parent
+ module_parent_name ? ActiveSupport::Inflector.constantize(module_parent_name) : Object
+ end
+
def parent
- parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ `Module#parent` has been renamed to `module_parent`.
+ `parent` is deprecated and will be removed in Rails 6.1.
+ MSG
+ module_parent
end
# Returns all the parents of this module according to its name, ordered from
@@ -40,15 +60,15 @@ class Module
# end
# X = M::N
#
- # M.parents # => [Object]
- # M::N.parents # => [M, Object]
- # X.parents # => [M, Object]
- def parents
+ # M.module_parents # => [Object]
+ # M::N.module_parents # => [M, Object]
+ # X.module_parents # => [M, Object]
+ def module_parents
parents = []
- if parent_name
- parts = parent_name.split('::')
+ if module_parent_name
+ parts = module_parent_name.split("::")
until parts.empty?
- parents << ActiveSupport::Inflector.constantize(parts * '::')
+ parents << ActiveSupport::Inflector.constantize(parts * "::")
parts.pop
end
end
@@ -56,7 +76,11 @@ class Module
parents
end
- def local_constants #:nodoc:
- constants(false)
+ def parents
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ `Module#parents` has been renamed to `module_parents`.
+ `parents` is deprecated and will be removed in Rails 6.1.
+ MSG
+ module_parents
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
deleted file mode 100644
index 1fde3db070..0000000000
--- a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require 'active_support/deprecation'
-
-ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb
deleted file mode 100644
index 65525013db..0000000000
--- a/activesupport/lib/active_support/core_ext/module/qualified_const.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'active_support/core_ext/string/inflections'
-
-#--
-# Allows code reuse in the methods below without polluting Module.
-#++
-module QualifiedConstUtils
- def self.raise_if_absolute(path)
- raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
- end
-
- def self.names(path)
- path.split('::')
- end
-end
-
-##
-# Extends the API for constants to be able to deal with qualified names. Arguments
-# are assumed to be relative to the receiver.
-#
-#--
-# Qualified names are required to be relative because we are extending existing
-# methods that expect constant names, ie, relative paths of length 1. For example,
-# Object.const_get('::String') raises NameError and so does qualified_const_get.
-#++
-class Module
- def qualified_const_defined?(path, search_parents=true)
- QualifiedConstUtils.raise_if_absolute(path)
-
- QualifiedConstUtils.names(path).inject(self) do |mod, name|
- return unless mod.const_defined?(name, search_parents)
- mod.const_get(name)
- end
- return true
- end
-
- def qualified_const_get(path)
- QualifiedConstUtils.raise_if_absolute(path)
-
- QualifiedConstUtils.names(path).inject(self) do |mod, name|
- mod.const_get(name)
- end
- end
-
- def qualified_const_set(path, value)
- QualifiedConstUtils.raise_if_absolute(path)
-
- const_name = path.demodulize
- mod_name = path.deconstantize
- mod = mod_name.empty? ? self : qualified_const_get(mod_name)
- mod.const_set(const_name, value)
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/module/reachable.rb b/activesupport/lib/active_support/core_ext/module/reachable.rb
index 5d3d0e9851..e9cbda5245 100644
--- a/activesupport/lib/active_support/core_ext/module/reachable.rb
+++ b/activesupport/lib/active_support/core_ext/module/reachable.rb
@@ -1,8 +1,11 @@
-require 'active_support/core_ext/module/anonymous'
-require 'active_support/core_ext/string/inflections'
+# frozen_string_literal: true
+
+require "active_support/core_ext/module/anonymous"
+require "active_support/core_ext/string/inflections"
class Module
def reachable? #:nodoc:
!anonymous? && name.safe_constantize.equal?(self)
end
+ deprecate :reachable?
end
diff --git a/activesupport/lib/active_support/core_ext/module/redefine_method.rb b/activesupport/lib/active_support/core_ext/module/redefine_method.rb
new file mode 100644
index 0000000000..5bd8e6e973
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/module/redefine_method.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class Module
+ # Marks the named method as intended to be redefined, if it exists.
+ # Suppresses the Ruby method redefinition warning. Prefer
+ # #redefine_method where possible.
+ def silence_redefinition_of_method(method)
+ if method_defined?(method) || private_method_defined?(method)
+ # This suppresses the "method redefined" warning; the self-alias
+ # looks odd, but means we don't need to generate a unique name
+ alias_method method, method
+ end
+ end
+
+ # Replaces the existing method definition, if there is one, with the passed
+ # block as its body.
+ def redefine_method(method, &block)
+ visibility = method_visibility(method)
+ silence_redefinition_of_method(method)
+ define_method(method, &block)
+ send(visibility, method)
+ end
+
+ # Replaces the existing singleton method definition, if there is one, with
+ # the passed block as its body.
+ def redefine_singleton_method(method, &block)
+ singleton_class.redefine_method(method, &block)
+ end
+
+ def method_visibility(method) # :nodoc:
+ case
+ when private_method_defined?(method)
+ :private
+ when protected_method_defined?(method)
+ :protected
+ else
+ :public
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb
index 52632d2c6b..97eb5f9eca 100644
--- a/activesupport/lib/active_support/core_ext/module/remove_method.rb
+++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+
+require "active_support/core_ext/module/redefine_method"
+
class Module
# Removes the named method, if it exists.
def remove_possible_method(method)
@@ -6,10 +10,8 @@ class Module
end
end
- # Replaces the existing method definition, if there is one, with the passed
- # block as its body.
- def redefine_method(method, &block)
- remove_possible_method(method)
- define_method(method, &block)
+ # Removes the named singleton method, if it exists.
+ def remove_possible_singleton_method(method)
+ singleton_class.remove_possible_method(method)
end
end