Merge pull request #6348 from LTe/no_global_depreactations

Allow ActiveSupport::Deprecation features to be used by rails applications and library authors
This commit is contained in:
Carlos Antonio da Silva 2012-09-13 04:47:03 -07:00
commit 01ef633f75
9 changed files with 412 additions and 98 deletions

View File

@ -1,5 +1,37 @@
## Rails 4.0.0 (unreleased) ##
* ActiveSupport::Deprecation is now a class. It is possible to create an instance
of deprecator. Backwards compatibility has been preserved.
You can choose which instance of the deprecator will be used.
deprecate :method_name, :deprecator => deprecator_instance
You can use ActiveSupport::Deprecation in your gem.
require 'active_support/deprecation'
require 'active_support/core_ext/module/deprecation'
class MyGem
def old_method
end
deprecate :old_method => :new_method, :deprecator => deprecator
def new_method
end
def self.deprecator
ActiveSupport::Deprecation.new('2.0', 'MyGem')
end
end
MyGem.new.old_method
DEPRECATION WARNING: old_method is deprecated and will be removed from MyGem 2.0
(use new_method instead). (called from <main> at file.rb:18)
*Piotr Niełacny & Robert Pankowecki*
* `ERB::Util.html_escape` encodes single quote as `#39`. Decimal form has better support in old browsers. *Kalys Osmonov*
* `ActiveSupport::Callbacks`: deprecate monkey patch of object callbacks.

View File

@ -1,10 +1,42 @@
require 'active_support/deprecation/method_wrappers'
class Module
# Declare that a method has been deprecated.
# deprecate :foo
# deprecate :bar => 'message'
# deprecate :foo, :bar, :baz => 'warning!', :qux => 'gone!'
#
# You can use custom deprecator instance
# deprecate :foo, :deprecator => MyLib::Deprecator.new
# deprecate :foo, :bar => "warning!", :deprecator => MyLib::Deprecator.new
#
# \Custom deprecators must respond to one method
# [deprecation_warning(deprecated_method_name, message, caller_backtrace)] will be called with the deprecated
# method name, the message it was declared
# with and caller_backtrace. Implement
# whatever warning behavior you like here.
#
# Example
# class MyLib::Deprecator
#
# def deprecation_warning(deprecated_method_name, message, caller_backtrace)
# message = "#{method_name} is deprecated and will be removed from MyLibrary | #{message}"
# Kernel.warn message
# end
#
# end
#
# module MyLib
# mattr_accessor :deprecator
# self.deprecator = Deprecator.new
# end
#
# When we deprecate method
# class MyLib::Bar
# deprecate :foo => "this is very old method", :deprecator => MyLib.deprecator
# end
#
# It will build deprecation message and invoke deprecator warning by calling
# MyLib.deprecator.deprecation_warning(:foo, "this is a very old method", caller)
def deprecate(*method_names)
ActiveSupport::Deprecation.deprecate_methods(self, *method_names)
end

View File

@ -1,19 +1,40 @@
require 'active_support/core_ext/module/deprecation'
require 'active_support/deprecation/instance_delegator'
require 'active_support/deprecation/behaviors'
require 'active_support/deprecation/reporting'
require 'active_support/deprecation/method_wrappers'
require 'active_support/deprecation/proxy_wrappers'
require 'singleton'
module ActiveSupport
module Deprecation
class << self
# The version the deprecated behavior will be removed, by default.
attr_accessor :deprecation_horizon
end
self.deprecation_horizon = '4.1'
# \Deprecation specifies the API used by Rails to deprecate
# methods, instance variables, objects and constants.
# The API depends on four methods:
#
# * +initialize+ which expects two parameters
# described below;
class Deprecation
include Singleton
include InstanceDelegator
include Behavior
include Reporting
include MethodWrapper
# By default, warnings are not silenced and debugging is off.
self.silenced = false
self.debug = false
# The version the deprecated behavior will be removed, by default.
attr_accessor :deprecation_horizon
# It accepts two parameters on initialization. The first is an version of library
# and the second is an library name
#
# == Example
#
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
def initialize(deprecation_horizon = '4.1', gem_name = 'Rails')
self.gem_name = gem_name
self.deprecation_horizon = deprecation_horizon
# By default, warnings are not silenced and debugging is off.
self.silenced = false
self.debug = false
end
end
end
end

View File

@ -1,8 +1,32 @@
require "active_support/notifications"
module ActiveSupport
module Deprecation
class << self
class Deprecation
# Default warning behaviors per Rails.env.
DEFAULT_BEHAVIORS = {
:stderr => Proc.new { |message, callstack|
$stderr.puts(message)
$stderr.puts callstack.join("\n ") if debug
},
:log => Proc.new { |message, callstack|
logger =
if defined?(Rails) && Rails.logger
Rails.logger
else
require 'active_support/logger'
ActiveSupport::Logger.new($stderr)
end
logger.warn message
logger.debug callstack.join("\n ") if debug
},
:notify => Proc.new { |message, callstack|
ActiveSupport::Notifications.instrument("deprecation.rails",
:message => message, :callstack => callstack)
},
:silence => Proc.new { |message, callstack| }
}
module Behavior
# Whether to print a backtrace along with the warning.
attr_accessor :debug
@ -16,9 +40,9 @@ module ActiveSupport
#
# Available behaviors:
#
# [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>.
# [+stderr+] Log all deprecation warnings to +$stderr+.
# [+log+] Log all deprecation warnings to +Rails.logger+.
# [+notify+] Use <tt>ActiveSupport::Notifications</tt> to notify +deprecation.rails+.
# [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
# [+silence+] Do nothing.
#
# Setting behaviors only affects deprecations that happen after boot time.
@ -28,36 +52,12 @@ module ActiveSupport
# ActiveSupport::Deprecation.behavior = :stderr
# ActiveSupport::Deprecation.behavior = [:stderr, :log]
# ActiveSupport::Deprecation.behavior = MyCustomHandler
# ActiveSupport::Deprecation.behavior = proc { |message, callstack|
# ActiveSupport::Deprecation.behavior = proc { |message, callstack|
# # custom stuff
# }
def behavior=(behavior)
@behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b }
end
end
# Default warning behaviors per Rails.env.
DEFAULT_BEHAVIORS = {
:stderr => Proc.new { |message, callstack|
$stderr.puts(message)
$stderr.puts callstack.join("\n ") if debug
},
:log => Proc.new { |message, callstack|
logger =
if defined?(Rails) && Rails.logger
Rails.logger
else
require 'active_support/logger'
ActiveSupport::Logger.new($stderr)
end
logger.warn message
logger.debug callstack.join("\n ") if debug
},
:notify => Proc.new { |message, callstack|
ActiveSupport::Notifications.instrument("deprecation.rails",
:message => message, :callstack => callstack)
},
:silence => Proc.new { |message, callstack| }
}
end
end

View File

@ -0,0 +1,24 @@
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/delegation'
module ActiveSupport
class Deprecation
module InstanceDelegator
def self.included(base)
base.extend(ClassMethods)
base.public_class_method :new
end
module ClassMethods
def include(included_module)
included_module.instance_methods.each { |m| method_added(m) }
super
end
def method_added(method_name)
singleton_class.delegate(method_name, to: :instance)
end
end
end
end
end

View File

@ -2,45 +2,41 @@ require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/array/extract_options'
module ActiveSupport
module Deprecation
# Declare that a method has been deprecated.
#
# module Fred
# extend self
#
# def foo; end
# def bar; end
# def baz; end
# end
#
# ActiveSupport::Deprecation.deprecate_methods(Fred, :foo, bar: :qux, baz: 'use Bar#baz instead')
# # => [:foo, :bar, :baz]
#
# Fred.foo
# # => "DEPRECATION WARNING: foo is deprecated and will be removed from Rails 4.1."
#
# Fred.bar
# # => "DEPRECATION WARNING: bar is deprecated and will be removed from Rails 4.1 (use qux instead)."
#
# Fred.baz
# # => "DEPRECATION WARNING: baz is deprecated and will be removed from Rails 4.1 (use Bar#baz instead)."
def self.deprecate_methods(target_module, *method_names)
options = method_names.extract_options!
method_names += options.keys
class Deprecation
module MethodWrapper
# Declare that a method has been deprecated.
#
# module Fred
# extend self
#
# def foo; end
# def bar; end
# def baz; end
# end
#
# ActiveSupport::Deprecation.deprecate_methods(Fred, :foo, bar: :qux, baz: 'use Bar#baz instead')
# # => [:foo, :bar, :baz]
#
# Fred.foo
# # => "DEPRECATION WARNING: foo is deprecated and will be removed from Rails 4.1."
#
# Fred.bar
# # => "DEPRECATION WARNING: bar is deprecated and will be removed from Rails 4.1 (use qux instead)."
#
# Fred.baz
# # => "DEPRECATION WARNING: baz is deprecated and will be removed from Rails 4.1 (use Bar#baz instead)."
def deprecate_methods(target_module, *method_names)
options = method_names.extract_options!
deprecator = options.delete(:deprecator) || ActiveSupport::Deprecation.instance
method_names += options.keys
method_names.each do |method_name|
target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation|
target_module.module_eval(<<-end_eval, __FILE__, __LINE__ + 1)
def #{target}_with_deprecation#{punctuation}(*args, &block)
::ActiveSupport::Deprecation.warn(
::ActiveSupport::Deprecation.deprecated_method_warning(
:#{method_name},
#{options[method_name].inspect}),
caller
)
send(:#{target}_without_deprecation#{punctuation}, *args, &block)
method_names.each do |method_name|
target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation|
target_module.send(:define_method, "#{target}_with_deprecation#{punctuation}") do |*args, &block|
deprecator.deprecation_warning(method_name, options[method_name], caller)
send(:"#{target}_without_deprecation#{punctuation}", *args, &block)
end
end_eval
end
end
end
end

View File

@ -1,7 +1,7 @@
require 'active_support/inflector/methods'
module ActiveSupport
module Deprecation
class Deprecation
class DeprecationProxy #:nodoc:
def self.new(*args, &block)
object = args.first
@ -25,10 +25,21 @@ module ActiveSupport
end
end
class DeprecatedObjectProxy < DeprecationProxy #:nodoc:
def initialize(object, message)
# This DeprecatedObjectProxy transforms object to depracated object.
#
# Example
# @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!")
# Example with custom deprecator
# @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance)
#
# When someone execute any method expect +inspect+ on proxy object this will trigger +warn+ method on +deprecator_instance+
#
# Default deprecator is ActiveSupport::Deprecation
class DeprecatedObjectProxy < DeprecationProxy
def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance)
@object = object
@message = message
@deprecator = deprecator
end
private
@ -37,15 +48,38 @@ module ActiveSupport
end
def warn(callstack, called, args)
ActiveSupport::Deprecation.warn(@message, callstack)
@deprecator.warn(@message, callstack)
end
end
# Stand-in for <tt>@request</tt>, <tt>@attributes</tt>, <tt>@params</tt>, etc.
# which emits deprecation warnings on any method call (except +inspect+).
class DeprecatedInstanceVariableProxy < DeprecationProxy #:nodoc:
def initialize(instance, method, var = "@#{method}")
@instance, @method, @var = instance, method, var
# This DeprecatedInstanceVariableProxy transforms instance variable to depracated instance variable.
#
# Example
# class Example
# def initialize(deprecator)
# @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator)
# @_request = :a_request
# end
#
# def request
# @_request
# end
#
# def old_request
# @request
# end
# end
#
# When someone execute any method on @request variable this will trigger +warn+ method on +deprecator_instance+
# and will fetch @_request variable via +request+ method and execute the same method on non-proxy instance variable.
#
# Default deprecator is ActiveSupport::Deprecation
class DeprecatedInstanceVariableProxy < DeprecationProxy
def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance)
@instance = instance
@method = method
@var = var
@deprecator = deprecator
end
private
@ -54,14 +88,24 @@ module ActiveSupport
end
def warn(callstack, called, args)
ActiveSupport::Deprecation.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack)
@deprecator.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack)
end
end
class DeprecatedConstantProxy < DeprecationProxy #:nodoc:all
def initialize(old_const, new_const)
# This DeprecatedConstantProxy transforms constant to depracated constant.
#
# Example
# OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST')
# Example with custom deprecator
# OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance)
# When someone use old constant this will trigger +warn+ method on +deprecator_instance+
#
# Default deprecator is ActiveSupport::Deprecation
class DeprecatedConstantProxy < DeprecationProxy
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance)
@old_const = old_const
@new_const = new_const
@deprecator = deprecator
end
def class
@ -74,7 +118,7 @@ module ActiveSupport
end
def warn(callstack, called, args)
ActiveSupport::Deprecation.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack)
@deprecator.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack)
end
end
end

View File

@ -1,7 +1,10 @@
module ActiveSupport
module Deprecation
class << self
class Deprecation
module Reporting
# Whether to print a message (silent mode)
attr_accessor :silenced
# Name of gem where method is deprecated
attr_accessor :gem_name
# Outputs a deprecation warning to the output configured by
# <tt>ActiveSupport::Deprecation.behavior</tt>.
@ -31,16 +34,29 @@ module ActiveSupport
@silenced = old_silenced
end
def deprecated_method_warning(method_name, message = nil)
warning = "#{method_name} is deprecated and will be removed from Rails #{deprecation_horizon}"
case message
when Symbol then "#{warning} (use #{message} instead)"
when String then "#{warning} (#{message})"
else warning
def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = caller)
deprecated_method_warning(deprecated_method_name, message).tap do |message|
warn(message, caller_backtrace)
end
end
private
# Outputs a deprecation warning message
# ActiveSupport::Deprecation.deprecated_method_warning(:method_name)
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}"
# ActiveSupport::Deprecation.deprecated_method_warning(:method_name, :another_method)
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)"
# ActiveSupport::Deprecation.deprecated_method_warning(:method_name, "Optional message")
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)"
def deprecated_method_warning(method_name, message = nil)
warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}"
case message
when Symbol then "#{warning} (use #{message} instead)"
when String then "#{warning} (#{message})"
else warning
end
end
def deprecation_message(callstack, message = nil)
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
message += '.' unless message =~ /\.$/

View File

@ -104,6 +104,17 @@ class DeprecationTest < ActiveSupport::TestCase
assert_match(/call stack!/, content)
end
def test_default_stderr_behavior_with_warn_method
ActiveSupport::Deprecation.behavior = :stderr
content = capture(:stderr) {
ActiveSupport::Deprecation.warn('Instance error!', ['instance call stack!'])
}
assert_match(/Instance error!/, content)
assert_match(/instance call stack!/, content)
end
def test_default_silence_behavior
ActiveSupport::Deprecation.behavior = :silence
behavior = ActiveSupport::Deprecation.behavior.first
@ -186,4 +197,142 @@ class DeprecationTest < ActiveSupport::TestCase
def test_deprecation_with_explicit_message
assert_deprecated(/you now need to do something extra for this one/) { @dtc.d }
end
def test_deprecation_in_other_object
messages = []
klass = Class.new do
delegate :warn, :behavior=, to: ActiveSupport::Deprecation
end
o = klass.new
o.behavior = Proc.new { |message, callstack| messages << message }
assert_difference("messages.size") do
o.warn("warning")
end
end
def test_deprecated_method_with_custom_method_warning
deprecator = deprecator_with_messages
class << deprecator
private
def deprecated_method_warning(method, message)
"deprecator.deprecated_method_warning.#{method}"
end
end
deprecatee = Class.new do
def method
end
deprecate :method, deprecator: deprecator
end
deprecatee.new.method
assert deprecator.messages.first.match("DEPRECATION WARNING: deprecator.deprecated_method_warning.method")
end
def test_deprecate_with_custom_deprecator
custom_deprecator = mock('Deprecator') do
expects(:deprecation_warning)
end
klass = Class.new do
def method
end
deprecate :method, deprecator: custom_deprecator
end
klass.new.method
end
def test_deprecated_constant_with_deprecator_given
deprecator = deprecator_with_messages
klass = Class.new
klass.const_set(:OLD, ActiveSupport::Deprecation::DeprecatedConstantProxy.new('klass::OLD', 'Object', deprecator) )
assert_difference("deprecator.messages.size") do
klass::OLD.to_s
end
end
def test_deprecated_instance_variable_with_instance_deprecator
deprecator = deprecator_with_messages
klass = Class.new() do
def initialize(deprecator)
@request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator)
@_request = :a_request
end
def request; @_request end
def old_request; @request end
end
assert_difference("deprecator.messages.size") { klass.new(deprecator).old_request.to_s }
end
def test_deprecated_instance_variable_with_given_deprecator
deprecator = deprecator_with_messages
klass = Class.new do
define_method(:initialize) do
@request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator)
@_request = :a_request
end
def request; @_request end
def old_request; @request end
end
assert_difference("deprecator.messages.size") { klass.new.old_request.to_s }
end
def test_delegate_deprecator_instance
klass = Class.new do
attr_reader :last_message
delegate :warn, :behavior=, to: ActiveSupport::Deprecation
def initialize
self.behavior = [Proc.new { |message| @last_message = message }]
end
def deprecated_method
warn(deprecated_method_warning(:deprecated_method, "You are calling deprecated method"))
end
private
def deprecated_method_warning(method_name, message = nil)
message || "#{method_name} is deprecated and will be removed from This Library"
end
end
object = klass.new
object.deprecated_method
assert_match(/You are calling deprecated method/, object.last_message)
end
def test_default_gem_name
deprecator = ActiveSupport::Deprecation.new
deprecator.send(:deprecated_method_warning, :deprecated_method, "You are calling deprecated method").tap do |message|
assert_match(/is deprecated and will be removed from Rails/, message)
end
end
def test_custom_gem_name
deprecator = ActiveSupport::Deprecation.new('2.0', 'Custom')
deprecator.send(:deprecated_method_warning, :deprecated_method, "You are calling deprecated method").tap do |message|
assert_match(/is deprecated and will be removed from Custom/, message)
end
end
private
def deprecator_with_messages
klass = Class.new(ActiveSupport::Deprecation)
deprecator = klass.new
deprecator.behavior = Proc.new{|message, callstack| deprecator.messages << message}
def deprecator.messages
@messages ||= []
end
deprecator
end
end