1 require 'yaml'
2
3 module ActiveSupport
4 module Deprecation #:nodoc:
5 mattr_accessor :debug
6 self.debug = false
7
8 # Choose the default warn behavior according to RAILS_ENV.
9 # Ignore deprecation warnings in production.
10 DEFAULT_BEHAVIORS = {
11 'test' => Proc.new { |message, callstack|
12 $stderr.puts(message)
13 $stderr.puts callstack.join("\n ") if debug
14 },
15 'development' => Proc.new { |message, callstack|
16 logger = (defined?(Rails) && Rails.logger) ? Rails.logger : Logger.new($stderr)
17 logger.warn message
18 logger.debug callstack.join("\n ") if debug
19 }
20 }
21
22 class << self
23 def warn(message = nil, callstack = caller)
24 behavior.call(deprecation_message(callstack, message), callstack) if behavior && !silenced?
25 end
26
27 def default_behavior
28 if defined?(RAILS_ENV)
29 DEFAULT_BEHAVIORS[RAILS_ENV.to_s]
30 else
31 DEFAULT_BEHAVIORS['test']
32 end
33 end
34
35 # Have deprecations been silenced?
36 def silenced?
37 @silenced = false unless defined?(@silenced)
38 @silenced
39 end
40
41 # Silence deprecation warnings within the block.
42 def silence
43 old_silenced, @silenced = @silenced, true
44 yield
45 ensure
46 @silenced = old_silenced
47 end
48
49 attr_writer :silenced
50
51
52 private
53 def deprecation_message(callstack, message = nil)
54 message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
55 message += '.' unless message =~ /\.$/
56 "DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}"
57 end
58
59 def deprecation_caller_message(callstack)
60 file, line, method = extract_callstack(callstack)
61 if file
62 if line && method
63 "(called from #{method} at #{file}:#{line})"
64 else
65 "(called from #{file}:#{line})"
66 end
67 end
68 end
69
70 def extract_callstack(callstack)
71 if md = callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
72 md.captures
73 else
74 callstack.first
75 end
76 end
77 end
78
79 # Behavior is a block that takes a message argument.
80 mattr_accessor :behavior
81 self.behavior = default_behavior
82
83 # Warnings are not silenced by default.
84 self.silenced = false
85
86 module ClassMethods #:nodoc:
87 # Declare that a method has been deprecated.
88 def deprecate(*method_names)
89 options = method_names.extract_options!
90 method_names = method_names + options.keys
91 method_names.each do |method_name|
92 alias_method_chain(method_name, :deprecation) do |target, punctuation|
93 class_eval(<<-EOS, __FILE__, __LINE__ + 1)
94 def #{target}_with_deprecation#{punctuation}(*args, &block) # def generate_secret_with_deprecation(*args, &block)
95 ::ActiveSupport::Deprecation.warn( # ::ActiveSupport::Deprecation.warn(
96 self.class.deprecated_method_warning( # self.class.deprecated_method_warning(
97 :#{method_name}, # :generate_secret,
98 #{options[method_name].inspect}), # "You should use ActiveSupport::SecureRandom.hex(64)"),
99 caller # caller
100 ) # )
101 send(:#{target}_without_deprecation#{punctuation}, *args, &block) # send(:generate_secret_without_deprecation, *args, &block)
102 end # end
103 EOS
104 end
105 end
106 end
107
108 def deprecated_method_warning(method_name, message=nil)
109 warning = "#{method_name} is deprecated and will be removed from Rails #{deprecation_horizon}"
110 case message
111 when Symbol then "#{warning} (use #{message} instead)"
112 when String then "#{warning} (#{message})"
113 else warning
114 end
115 end
116
117 def deprecation_horizon
118 '2.3'
119 end
120 end
121
122 class DeprecationProxy #:nodoc:
123 def self.new(*args, &block)
124 object = args.first
125
126 return object unless object
127 super
128 end
129
130 silence_warnings do
131 instance_methods.each { |m| undef_method m unless m =~ /^__/ }
132 end
133
134 # Don't give a deprecation warning on inspect since test/unit and error
135 # logs rely on it for diagnostics.
136 def inspect
137 target.inspect
138 end
139
140 private
141 def method_missing(called, *args, &block)
142 warn caller, called, args
143 target.__send__(called, *args, &block)
144 end
145 end
146
147 class DeprecatedObjectProxy < DeprecationProxy
148 def initialize(object, message)
149 @object = object
150 @message = message
151 end
152
153 private
154 def target
155 @object
156 end
157
158 def warn(callstack, called, args)
159 ActiveSupport::Deprecation.warn(@message, callstack)
160 end
161 end
162
163 # Stand-in for <tt>@request</tt>, <tt>@attributes</tt>, <tt>@params</tt>, etc.
164 # which emits deprecation warnings on any method call (except +inspect+).
165 class DeprecatedInstanceVariableProxy < DeprecationProxy #:nodoc:
166 def initialize(instance, method, var = "@#{method}")
167 @instance, @method, @var = instance, method, var
168 end
169
170 private
171 def target
172 @instance.__send__(@method)
173 end
174
175 def warn(callstack, called, args)
176 ActiveSupport::Deprecation.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack)
177 end
178 end
179
180 class DeprecatedConstantProxy < DeprecationProxy #:nodoc:
181 def initialize(old_const, new_const)
182 @old_const = old_const
183 @new_const = new_const
184 end
185
186 def class
187 target.class
188 end
189
190 private
191 def target
192 @new_const.to_s.constantize
193 end
194
195 def warn(callstack, called, args)
196 ActiveSupport::Deprecation.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack)
197 end
198 end
199 end
200 end
201
202 class Module
203 include ActiveSupport::Deprecation::ClassMethods
204 end