Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify intentions around method redefinitions #29233

Merged
merged 2 commits into from Sep 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions actioncable/lib/action_cable/remote_connections.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require "active_support/core_ext/module/redefine_method"

module ActionCable
# If you need to disconnect a given connection, you can go through the
# RemoteConnections. You can find the connections you're looking for by
Expand Down
2 changes: 1 addition & 1 deletion actionpack/lib/action_controller/metal/renderers.rb
Expand Up @@ -85,7 +85,7 @@ def self.add(key, &block)
def self.remove(key)
RENDERERS.delete(key.to_sym)
method_name = _render_with_renderer_method_name(key)
remove_method(method_name) if method_defined?(method_name)
remove_possible_method(method_name)
end

def self._render_with_renderer_method_name(key)
Expand Down
3 changes: 2 additions & 1 deletion actionpack/lib/action_controller/test_case.rb
Expand Up @@ -4,6 +4,7 @@
require "active_support/core_ext/hash/conversions"
require "active_support/core_ext/object/to_query"
require "active_support/core_ext/module/anonymous"
require "active_support/core_ext/module/redefine_method"
require "active_support/core_ext/hash/keys"
require "active_support/testing/constant_lookup"
require_relative "template_assertions"
Expand All @@ -19,7 +20,7 @@ module Live
# the database on the main thread, so they could open a txn, then the
# controller thread will open a new connection and try to access data
# that's only visible to the main thread's txn. This is the problem in #23483.
remove_method :new_controller_thread
silence_redefinition_of_method :new_controller_thread
def new_controller_thread # :nodoc:
yield
end
Expand Down
3 changes: 2 additions & 1 deletion actionpack/lib/action_dispatch/routing/route_set.rb
Expand Up @@ -3,6 +3,7 @@
require_relative "../journey"
require "active_support/core_ext/object/to_query"
require "active_support/core_ext/hash/slice"
require "active_support/core_ext/module/redefine_method"
require "active_support/core_ext/module/remove_method"
require "active_support/core_ext/array/extract_options"
require "action_controller/metal/exceptions"
Expand Down Expand Up @@ -546,7 +547,7 @@ def url_options; {}; end

# plus a singleton class method called _routes ...
included do
singleton_class.send(:redefine_method, :_routes) { routes }
redefine_singleton_method(:_routes) { routes }
end

# And an instance method _routes. Note that
Expand Down
4 changes: 2 additions & 2 deletions actionview/lib/action_view/layouts.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true

require_relative "rendering"
require "active_support/core_ext/module/remove_method"
require "active_support/core_ext/module/redefine_method"

module ActionView
# Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
Expand Down Expand Up @@ -279,7 +279,7 @@ def layout(layout, conditions = {})
# If a layout is not explicitly mentioned then look for a layout with the controller's name.
# if nothing is found then try same procedure to find super class's layout.
def _write_layout_method # :nodoc:
remove_possible_method(:_layout)
silence_redefinition_of_method(:_layout)

prefixes = /\blayouts/.match?(_implied_layout_name) ? [] : ["layouts"]
default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, [], { formats: formats }).first || super"
Expand Down
4 changes: 2 additions & 2 deletions actionview/lib/action_view/test_case.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require "active_support/core_ext/module/remove_method"
require "active_support/core_ext/module/redefine_method"
require "action_controller"
require "action_controller/test_case"
require "action_view"
Expand Down Expand Up @@ -171,7 +171,7 @@ def document_root_element

def say_no_to_protect_against_forgery!
_helpers.module_eval do
remove_possible_method :protect_against_forgery?
silence_redefinition_of_method :protect_against_forgery?
def protect_against_forgery?
false
end
Expand Down
4 changes: 2 additions & 2 deletions activemodel/lib/active_model/naming.rb
Expand Up @@ -2,7 +2,7 @@

require "active_support/core_ext/hash/except"
require "active_support/core_ext/module/introspection"
require "active_support/core_ext/module/remove_method"
require "active_support/core_ext/module/redefine_method"

module ActiveModel
class Name
Expand Down Expand Up @@ -218,7 +218,7 @@ def _singularize(string)
# provided method below, or rolling your own is required.
module Naming
def self.extended(base) #:nodoc:
base.remove_possible_method :model_name
base.silence_redefinition_of_method :model_name
base.delegate :model_name, to: :class
end

Expand Down
5 changes: 2 additions & 3 deletions activerecord/lib/active_record/nested_attributes.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "active_support/core_ext/hash/except"
require "active_support/core_ext/module/redefine_method"
require "active_support/core_ext/object/try"
require "active_support/core_ext/hash/indifferent_access"

Expand Down Expand Up @@ -355,9 +356,7 @@ def accepts_nested_attributes_for(*attr_names)
# associations are just regular associations.
def generate_association_writer(association_name, type)
generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
if method_defined?(:#{association_name}_attributes=)
remove_method(:#{association_name}_attributes=)
end
silence_redefinition_of_method :#{association_name}_attributes=
def #{association_name}_attributes=(attributes)
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
end
Expand Down
25 changes: 11 additions & 14 deletions activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true

require_relative "../kernel/singleton_class"
require_relative "../module/remove_method"
require_relative "../module/redefine_method"
require_relative "../array/extract_options"

class Class
Expand Down Expand Up @@ -92,25 +92,23 @@ def class_attribute(*attrs)
default_value = options.fetch(:default, nil)

attrs.each do |name|
remove_possible_singleton_method(name)
singleton_class.silence_redefinition_of_method(name)
define_singleton_method(name) { nil }

remove_possible_singleton_method("#{name}?")
singleton_class.silence_redefinition_of_method("#{name}?")
define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate

ivar = "@#{name}"

remove_possible_singleton_method("#{name}=")
singleton_class.silence_redefinition_of_method("#{name}=")
define_singleton_method("#{name}=") do |val|
singleton_class.class_eval do
remove_possible_method(name)
define_method(name) { val }
redefine_method(name) { val }
end

if singleton_class?
class_eval do
remove_possible_method(name)
define_method(name) do
redefine_method(name) do
if instance_variable_defined? ivar
instance_variable_get ivar
else
Expand All @@ -123,22 +121,21 @@ def class_attribute(*attrs)
end

if instance_reader
remove_possible_method name
define_method(name) do
redefine_method(name) do
if instance_variable_defined?(ivar)
instance_variable_get ivar
else
self.class.public_send name
end
end

remove_possible_method "#{name}?"
define_method("#{name}?") { !!public_send(name) } if instance_predicate
redefine_method("#{name}?") { !!public_send(name) } if instance_predicate
end

if instance_writer
remove_possible_method "#{name}="
attr_writer name
redefine_method("#{name}=") do |val|
instance_variable_set ivar, val
end
end

unless default_value.nil?
Expand Down
14 changes: 5 additions & 9 deletions activesupport/lib/active_support/core_ext/date/conversions.rb
Expand Up @@ -3,7 +3,7 @@
require "date"
require_relative "../../inflector/methods"
require_relative "zones"
require_relative "../module/remove_method"
require_relative "../module/redefine_method"

class Date
DATE_FORMATS = {
Expand All @@ -19,14 +19,6 @@ class Date
iso8601: lambda { |date| date.iso8601 }
}

# Ruby 1.9 has Date#to_time which converts to localtime only.
remove_method :to_time

# Ruby 1.9 has Date#xmlschema which converts to a string without the time
# component. This removal may generate an issue on FreeBSD, that's why we
# need to use remove_possible_method here
remove_possible_method :xmlschema

# Convert to a formatted string. See DATE_FORMATS for predefined formats.
#
# This method is aliased to <tt>to_s</tt>.
Expand Down Expand Up @@ -72,6 +64,8 @@ def readable_inspect
alias_method :default_inspect, :inspect
alias_method :inspect, :readable_inspect

silence_redefinition_of_method :to_time

# Converts a Date instance to a Time, where the time is set to the beginning of the day.
# The timezone can be either :local or :utc (default :local).
#
Expand All @@ -89,6 +83,8 @@ def to_time(form = :local)
::Time.send(form, year, month, day)
end

silence_redefinition_of_method :xmlschema

# Returns a string which represents the time in used time zone as DateTime
# defined by XML Schema:
#
Expand Down
@@ -1,12 +1,12 @@
# frozen_string_literal: true

require_relative "../date_and_time/compatibility"
require_relative "../module/remove_method"
require_relative "../module/redefine_method"

class DateTime
include DateAndTime::Compatibility

remove_possible_method :to_time
silence_redefinition_of_method :to_time

# Either return an instance of `Time` with the same UTC offset
# as +self+ or an instance of `Time` representing the same time
Expand Down
1 change: 1 addition & 0 deletions activesupport/lib/active_support/core_ext/module.rb
Expand Up @@ -10,4 +10,5 @@
require_relative "module/concerning"
require_relative "module/delegation"
require_relative "module/deprecation"
require_relative "module/redefine_method"
require_relative "module/remove_method"
@@ -0,0 +1,49 @@
# frozen_string_literal: true

class Module
if RUBY_VERSION >= "2.3"
# 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
else
def silence_redefinition_of_method(method)
if method_defined?(method) || private_method_defined?(method)
alias_method :__rails_redefine, method
remove_method :__rails_redefine
end
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
26 changes: 3 additions & 23 deletions activesupport/lib/active_support/core_ext/module/remove_method.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require_relative "redefine_method"

class Module
# Removes the named method, if it exists.
def remove_possible_method(method)
Expand All @@ -10,28 +12,6 @@ def remove_possible_method(method)

# Removes the named singleton method, if it exists.
def remove_possible_singleton_method(method)
singleton_class.instance_eval do
remove_possible_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)
remove_possible_method(method)
define_method(method, &block)
send(visibility, method)
end

def method_visibility(method) # :nodoc:
case
when private_method_defined?(method)
:private
when protected_method_defined?(method)
:protected
else
:public
end
singleton_class.remove_possible_method(method)
end
end
Expand Up @@ -2,6 +2,7 @@

require "erb"
require_relative "../kernel/singleton_class"
require_relative "../module/redefine_method"
require_relative "../../multibyte/unicode"

class ERB
Expand All @@ -23,13 +24,12 @@ def html_escape(s)
unwrapped_html_escape(s).html_safe
end

# Aliasing twice issues a warning "discarding old...". Remove first to avoid it.
remove_method(:h)
silence_redefinition_of_method :h
alias h html_escape

module_function :h

singleton_class.send(:remove_method, :html_escape)
singleton_class.silence_redefinition_of_method :html_escape
module_function :html_escape

# HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
Expand Down
@@ -1,12 +1,12 @@
# frozen_string_literal: true

require_relative "../date_and_time/compatibility"
require_relative "../module/remove_method"
require_relative "../module/redefine_method"

class Time
include DateAndTime::Compatibility

remove_possible_method :to_time
silence_redefinition_of_method :to_time

# Either return +self+ or the time in the local system timezone depending
# on the setting of +ActiveSupport.to_time_preserves_timezone+.
Expand Down
3 changes: 2 additions & 1 deletion activesupport/lib/active_support/core_ext/uri.rb
Expand Up @@ -5,8 +5,9 @@
parser = URI::Parser.new

unless str == parser.unescape(parser.escape(str))
require "active_support/core_ext/module/redefine_method"
URI::Parser.class_eval do
remove_method :unescape
silence_redefinition_of_method :unescape
def unescape(str, escaped = /%[a-fA-F\d]{2}/)
# TODO: Are we actually sure that ASCII == UTF-8?
# YK: My initial experiments say yes, but let's be sure please
Expand Down