cheap_advice 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +70 -0
- data/Rakefile +79 -0
- data/VERSION +1 -0
- data/cheap_advice.slides.textile +257 -0
- data/example/ex01.rb +51 -0
- data/example/ex02-trace-0.yml +17 -0
- data/example/ex02-trace-1.yml +17 -0
- data/example/ex02-trace-2.yml +23 -0
- data/example/ex02-trace-4.yml +20 -0
- data/example/ex02-trace-5.yml +22 -0
- data/example/ex02-trace-6.yml +25 -0
- data/example/ex02.rb +99 -0
- data/lib/cheap_advice.rb +511 -0
- data/lib/cheap_advice/configuration.rb +217 -0
- data/lib/cheap_advice/trace.rb +228 -0
- data/spec/cheap_advice_spec.rb +342 -0
- data/spec/spec_helper.rb +12 -0
- metadata +192 -0
data/example/ex01.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
$: << File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'cheap_advice'
|
4
|
+
require 'pp'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
class MyClass
|
8
|
+
def foo
|
9
|
+
42
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class MyOtherClass
|
14
|
+
def bar
|
15
|
+
43
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
a = MyClass.new
|
20
|
+
b = MyOtherClass.new
|
21
|
+
|
22
|
+
trace_advice = CheapAdvice.new(:around) do | ar, body |
|
23
|
+
ar.advice[:log] << "#{Time.now.iso8601(6)} " <<
|
24
|
+
"#{ar.rcvr.class} #{ar.meth} #{ar.rcvr.object_id}\n"
|
25
|
+
body.call
|
26
|
+
ar.advice[:log] << "#{Time.now.iso8601(6)} " <<
|
27
|
+
"#{ar.rcvr.class} #{ar.meth} #{ar.rcvr.object_id} " <<
|
28
|
+
"=> #{ar.result.inspect}\n"
|
29
|
+
end
|
30
|
+
trace_advice[:log] = $stderr # File.open("trace.log", "a+")
|
31
|
+
|
32
|
+
puts "\nWithout advice:"
|
33
|
+
pp a.foo
|
34
|
+
pp b.bar
|
35
|
+
|
36
|
+
puts "\nWith advice enabled:"
|
37
|
+
trace_advice.advise!(MyClass, :foo)
|
38
|
+
trace_advice.advise!(MyOtherClass, :bar)
|
39
|
+
pp a.foo
|
40
|
+
pp b.bar
|
41
|
+
|
42
|
+
puts "\nWith advice disabled:"
|
43
|
+
trace_advice.disable!
|
44
|
+
pp a.foo
|
45
|
+
pp b.bar
|
46
|
+
|
47
|
+
puts "\nWith advice re-enabled:"
|
48
|
+
trace_advice.enable!
|
49
|
+
pp a.foo
|
50
|
+
pp b.bar
|
51
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
:advice:
|
3
|
+
~:
|
4
|
+
:enabled: true
|
5
|
+
:options:
|
6
|
+
:trace:
|
7
|
+
:logger:
|
8
|
+
:name: :default
|
9
|
+
:default:
|
10
|
+
:log_prefix: "YO "
|
11
|
+
|
12
|
+
MyClass:
|
13
|
+
:advice: trace
|
14
|
+
|
15
|
+
MyClass#foo: ~
|
16
|
+
MyClass#bar:
|
17
|
+
:options:
|
18
|
+
:trace:
|
19
|
+
:logger:
|
20
|
+
:name: :alternate
|
21
|
+
:log_after: false
|
22
|
+
:log_result: false
|
23
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
:advice:
|
3
|
+
~:
|
4
|
+
:enabled: false
|
5
|
+
:options:
|
6
|
+
:trace:
|
7
|
+
:logger:
|
8
|
+
:name: :default
|
9
|
+
|
10
|
+
MyClass:
|
11
|
+
:advice: trace
|
12
|
+
|
13
|
+
MyClass#foo:
|
14
|
+
:enabled: true
|
15
|
+
:options:
|
16
|
+
:trace:
|
17
|
+
:log_before: false
|
18
|
+
:log_after: true
|
19
|
+
:log_result: true
|
20
|
+
|
21
|
+
MyClass#bar: false
|
22
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
:advice:
|
3
|
+
~:
|
4
|
+
:enabled: false
|
5
|
+
:options:
|
6
|
+
:trace:
|
7
|
+
:logger:
|
8
|
+
:name: :default
|
9
|
+
|
10
|
+
MyClass:
|
11
|
+
:advice: trace
|
12
|
+
|
13
|
+
MyClass#foo:
|
14
|
+
:enabled: true
|
15
|
+
:options:
|
16
|
+
:trace:
|
17
|
+
:log_before: true
|
18
|
+
:log_args: true
|
19
|
+
:log_after: true
|
20
|
+
:log_result: false
|
21
|
+
|
22
|
+
MyClass#bar: false
|
23
|
+
|
24
|
+
MyClass#raise_error: true
|
25
|
+
|
data/example/ex02.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
$: << File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'cheap_advice'
|
4
|
+
require 'cheap_advice/configuration'
|
5
|
+
require 'cheap_advice/trace'
|
6
|
+
require 'yaml'
|
7
|
+
require 'benchmark'
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
=begin
|
11
|
+
gem 'ruby-debug'
|
12
|
+
require 'ruby-debug'
|
13
|
+
=end
|
14
|
+
|
15
|
+
##################################################
|
16
|
+
# Target
|
17
|
+
|
18
|
+
class MyClass
|
19
|
+
def foo(arg)
|
20
|
+
42 + arg
|
21
|
+
end
|
22
|
+
def bar(arg)
|
23
|
+
24 + arg
|
24
|
+
end
|
25
|
+
def raise_error(arg)
|
26
|
+
raise ArgumentError, "#{arg.inspect}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class MySubclass < MyClass
|
31
|
+
end
|
32
|
+
|
33
|
+
trace_advice = nil
|
34
|
+
trace_config = nil
|
35
|
+
|
36
|
+
Benchmark.bm(40) do | bm |
|
37
|
+
|
38
|
+
##################################################
|
39
|
+
# Advice
|
40
|
+
|
41
|
+
bm.report("trace_advice setup") do
|
42
|
+
trace_advice = CheapAdvice::Trace.new
|
43
|
+
|
44
|
+
# Configure Trace loggers by name.
|
45
|
+
trace_advice.logger[:default] = {
|
46
|
+
:target => File.open(File.expand_path("../ex02-default.log", __FILE__), "w"),
|
47
|
+
}
|
48
|
+
|
49
|
+
trace_advice.logger[:alternate] = {
|
50
|
+
:target => File.open(File.expand_path("../ex02-alternate.log", __FILE__), "w"),
|
51
|
+
:formatter => CheapAdvice::Trace::YamlFormatter
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
##################################################
|
56
|
+
# Advice Configuration
|
57
|
+
|
58
|
+
|
59
|
+
bm.report("trace_config setup") do
|
60
|
+
# Register Trace advice with configuration.
|
61
|
+
trace_config = CheapAdvice::Configuration.new
|
62
|
+
trace_config.advice[:trace] = trace_advice
|
63
|
+
end
|
64
|
+
|
65
|
+
config_yml = File.expand_path("../ex02-trace-*.yml", __FILE__)
|
66
|
+
|
67
|
+
Dir[config_yml].sort.each do | config_yml |
|
68
|
+
# Configure.
|
69
|
+
config_hash = YAML.load_file(config_yml)
|
70
|
+
trace_config.config = config_hash[:advice]
|
71
|
+
|
72
|
+
bm.report("configure! using #{File.basename(config_yml)}") do
|
73
|
+
trace_config.configure!
|
74
|
+
end
|
75
|
+
|
76
|
+
msg = "Using #{config_yml}"
|
77
|
+
# puts msg
|
78
|
+
trace_advice.log_all(msg)
|
79
|
+
|
80
|
+
##################################################
|
81
|
+
# Target activity
|
82
|
+
|
83
|
+
bm.report("Target activity") do
|
84
|
+
a = MyClass.new
|
85
|
+
a.foo(123)
|
86
|
+
a.bar(456)
|
87
|
+
b = MySubclass.new
|
88
|
+
b.foo(789)
|
89
|
+
b.bar(012)
|
90
|
+
begin
|
91
|
+
a.raise_error(:aSymbol)
|
92
|
+
rescue Exception
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
|
data/lib/cheap_advice.rb
ADDED
@@ -0,0 +1,511 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
# Provides cheap advice mechanism for Ruby.
|
4
|
+
# See: http://github.com/kstephens/cheap_advice
|
5
|
+
#
|
6
|
+
class CheapAdvice
|
7
|
+
EMPTY_Hash = { }.freeze
|
8
|
+
EMPTY_Array = [ ].freeze
|
9
|
+
EMPTY_String = ''.freeze
|
10
|
+
|
11
|
+
class Error < ::Exception; end
|
12
|
+
|
13
|
+
module Options
|
14
|
+
# Hash accesible by #[] and #[]=.
|
15
|
+
attr_accessor :options
|
16
|
+
def initialize
|
17
|
+
@mutex = Mutex.new
|
18
|
+
@options = { }
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](k)
|
22
|
+
# @mutex.synchronize do
|
23
|
+
@options[k]
|
24
|
+
# end
|
25
|
+
end
|
26
|
+
|
27
|
+
def []=(k, v)
|
28
|
+
# @mutex.synchronize do
|
29
|
+
@options[k] = v
|
30
|
+
# end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
include Options
|
34
|
+
|
35
|
+
# Procs called before, after, and around the original method.
|
36
|
+
attr_accessor :before, :after, :around
|
37
|
+
|
38
|
+
# Collection of Advised method bindings.
|
39
|
+
attr_accessor :advised
|
40
|
+
|
41
|
+
# Module or Array of Modules to extend new Advised objects with.
|
42
|
+
attr_accessor :advised_extend
|
43
|
+
|
44
|
+
NULL_PROC = lambda { | ar | }
|
45
|
+
NULL_AROUND_PROC = lambda { | ar, body | body.call }
|
46
|
+
|
47
|
+
# options:
|
48
|
+
# :before
|
49
|
+
# :after
|
50
|
+
# :around
|
51
|
+
def initialize *opts, &blk
|
52
|
+
super()
|
53
|
+
|
54
|
+
@advised = [ ]
|
55
|
+
@advised_for = { }
|
56
|
+
|
57
|
+
opts_hash = Hash === opts[-1] ? opts.pop : { }
|
58
|
+
opts_key = opts.shift
|
59
|
+
@options = opts_hash
|
60
|
+
|
61
|
+
@before = (opts_key == :before ? blk : opts_hash[:before]) ||
|
62
|
+
NULL_PROC
|
63
|
+
@after = (opts_key == :after ? blk : opts_hash[:after]) ||
|
64
|
+
NULL_PROC
|
65
|
+
@around = (opts_key == :around ? blk : opts_hash[:around]) ||
|
66
|
+
NULL_AROUND_PROC
|
67
|
+
|
68
|
+
@blk = blk
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Apply advice a method on a Module (or Class).
|
73
|
+
#
|
74
|
+
# The advised method is enabled immediately (this may change in a future release).
|
75
|
+
#
|
76
|
+
# Returns an Advised object that describes what method was advised.
|
77
|
+
#
|
78
|
+
# mod can be a String, a Module or an Array of either.
|
79
|
+
# method can be a String, a Symbol or an Array of either.
|
80
|
+
# if either are Arrays the result will be an Array of Advised objects.
|
81
|
+
#
|
82
|
+
# The type of method scope can be specified by:
|
83
|
+
# * :instance (default)
|
84
|
+
# * :class
|
85
|
+
# * :module
|
86
|
+
#
|
87
|
+
# Any additional Hash options are propagated to the Advised binding object, which
|
88
|
+
# can be accessed from the ActivationRecord passed to the Advice block(s).
|
89
|
+
#
|
90
|
+
# Examples:
|
91
|
+
# advice = CheapAdvice(:around) { | ar, body | ...; body.call; ... }
|
92
|
+
# advice.advise! MyClass, :instance_method, options_hash
|
93
|
+
# advice.advise! MyClass, :class_method, :class
|
94
|
+
#
|
95
|
+
# Each Advised object is extended with #advised_extend.
|
96
|
+
# The #advised Array lists all Advised object.
|
97
|
+
#
|
98
|
+
def advise! mod, method, *opts
|
99
|
+
return mod.map { | x | advise! x, method, *opts } if
|
100
|
+
Array === mod
|
101
|
+
return method.map { | x | advise! mod, x, *opts } if
|
102
|
+
Array === method
|
103
|
+
|
104
|
+
opts_hash = Hash === opts[-1] ? opts.pop : { }
|
105
|
+
kind = opts.shift
|
106
|
+
kind ||= :instance
|
107
|
+
|
108
|
+
method = method.to_sym
|
109
|
+
|
110
|
+
@mutex.synchronize do
|
111
|
+
unless @enabled_once
|
112
|
+
self.enabled!
|
113
|
+
@enabled_once = true
|
114
|
+
end
|
115
|
+
|
116
|
+
advised = advised_for mod, method, kind, opts_hash
|
117
|
+
|
118
|
+
advised.enable! # Should this really be automatically enabled??
|
119
|
+
|
120
|
+
advised
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Called once the first time this advice is enabled.
|
125
|
+
# Instances can override this method.
|
126
|
+
def enabled!
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
def advised_select mod, meth, kind
|
131
|
+
@advised.select do | ad |
|
132
|
+
(mod ? mod == ad.mod : true) &&
|
133
|
+
(meth ? meth == ad.meth : true) &&
|
134
|
+
(kind ? kind == ad.kind : true)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns the existing Advised binding or creates a new one.
|
139
|
+
def advised_for mod, meth, kind, opts
|
140
|
+
(@advised_for[[ mod, meth, kind ]] ||=
|
141
|
+
construct_advised_for(mod, meth, kind)).set_options!(opts)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Constructs an Advised binding from this Advice.
|
145
|
+
def construct_advised_for mod, meth, kind
|
146
|
+
advice = self
|
147
|
+
|
148
|
+
advised = Advised.new(advice, mod, meth, kind)
|
149
|
+
|
150
|
+
case @advised_extend
|
151
|
+
when nil
|
152
|
+
when Module
|
153
|
+
advised.extend(@advised_extend)
|
154
|
+
when Array
|
155
|
+
@advised_extend.each { | m | advised.extend(m) }
|
156
|
+
else
|
157
|
+
raise TypeError, "advised_extend: expected nil, Module or Array of Modules, given #{@advised_extend.class}"
|
158
|
+
end
|
159
|
+
|
160
|
+
advised.register_advice_methods!
|
161
|
+
|
162
|
+
advised.define_new_method!
|
163
|
+
|
164
|
+
@advised << advised
|
165
|
+
|
166
|
+
advised
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
# Disables all currently Advised methods.
|
171
|
+
def disable!
|
172
|
+
@mutex.synchronize do
|
173
|
+
@advised.each { | x | x.disable! }
|
174
|
+
end
|
175
|
+
self
|
176
|
+
end
|
177
|
+
alias :unadvise! :disable!
|
178
|
+
|
179
|
+
|
180
|
+
# Enables all currently Advised methods.
|
181
|
+
def enable!
|
182
|
+
@mutex.synchronize do
|
183
|
+
@advised.each { | x | x.enable! }
|
184
|
+
end
|
185
|
+
self
|
186
|
+
end
|
187
|
+
alias :readvise! :enable!
|
188
|
+
|
189
|
+
|
190
|
+
# Represents the application/binding of advice to a class and method.
|
191
|
+
class Advised
|
192
|
+
include Options
|
193
|
+
|
194
|
+
@@mutex = Mutex.new
|
195
|
+
@@advised_id ||= 0
|
196
|
+
|
197
|
+
# The Advice being applied to the Module and method.
|
198
|
+
attr_reader :advice
|
199
|
+
# The Module, method and kind (instance, class or module method)
|
200
|
+
attr_reader :mod, :meth, :kind
|
201
|
+
alias :cls :mod # Deprecated.
|
202
|
+
|
203
|
+
# The unique Advised id used to generate unique method names.
|
204
|
+
attr_reader :advised_id
|
205
|
+
|
206
|
+
# The name of the old and new method being patched in.
|
207
|
+
attr_reader :old_meth, :new_meth
|
208
|
+
|
209
|
+
# The name of the before, after and around methods.
|
210
|
+
attr_reader :before_meth, :after_meth, :around_meth
|
211
|
+
|
212
|
+
# True if the Advised methods are currently installed.
|
213
|
+
attr_reader :enabled
|
214
|
+
|
215
|
+
def initialize *args
|
216
|
+
@mutex = Mutex.new
|
217
|
+
@advice, @mod, @meth, @kind, @options = *args
|
218
|
+
|
219
|
+
case @kind
|
220
|
+
when :instance, :class, :module
|
221
|
+
else
|
222
|
+
raise ArgumentError, "invalid kind #{kind.inspect}"
|
223
|
+
end
|
224
|
+
|
225
|
+
@options ||= { }
|
226
|
+
|
227
|
+
@@mutex.synchronize do
|
228
|
+
@advised_id = @@advised_id += 1
|
229
|
+
end
|
230
|
+
|
231
|
+
@old_meth = :"__advice_old_#{@@advised_id}_#{@meth}"
|
232
|
+
@new_meth = :"__advice_new_#{@@advised_id}_#{@meth}"
|
233
|
+
|
234
|
+
@before_meth = :"__advice_before_#{@@advised_id}_#{@meth}"
|
235
|
+
@after_meth = :"__advice_after_#{@@advised_id}_#{@meth}"
|
236
|
+
@around_meth = :"__advice_around_#{@@advised_id}_#{@meth}"
|
237
|
+
|
238
|
+
@enabled =
|
239
|
+
@advice_methods_applied = false
|
240
|
+
end
|
241
|
+
|
242
|
+
def set_options! options
|
243
|
+
@options = options || { }
|
244
|
+
self
|
245
|
+
end
|
246
|
+
|
247
|
+
INSTANCE_SEP = '#'.freeze
|
248
|
+
MODULE_SEP = '.'.freeze
|
249
|
+
|
250
|
+
# The string name for the method.
|
251
|
+
# Returns "Foo#bar" for an instance method named :bar on class Foo.
|
252
|
+
# Returns "Foo.bar" for a class or module method named .bar on class Foo.
|
253
|
+
def meth_to_s
|
254
|
+
@meth_to_s ||=
|
255
|
+
"#{@mod}#{@kind == :instance ? INSTANCE_SEP : MODULE_SEP}#{@meth}".freeze
|
256
|
+
end
|
257
|
+
|
258
|
+
# True if the advice, mod, method and kind are equal.
|
259
|
+
def == x
|
260
|
+
return false unless self.class === x
|
261
|
+
@advice == x.advice && @mod == x.mod && @meth == x.meth && @kind == x.kind
|
262
|
+
end
|
263
|
+
|
264
|
+
# Support for Hash.
|
265
|
+
def hash
|
266
|
+
@advice.hash ^ @mod.hash ^ @meth.hash ^ @kind.hash
|
267
|
+
end
|
268
|
+
|
269
|
+
# Returns the target Module for the kind of method.
|
270
|
+
def mod_target
|
271
|
+
case @kind
|
272
|
+
when :instance
|
273
|
+
mod_resolve
|
274
|
+
when :class, :module
|
275
|
+
(class << mod_resolve; self; end)
|
276
|
+
else
|
277
|
+
raise ArgumentError, "mod_target: invalid kind #{kind.inspect}"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Resolves mod Strings to the actual target Module.
|
282
|
+
def mod_resolve
|
283
|
+
case @mod
|
284
|
+
when Module
|
285
|
+
@mod
|
286
|
+
when String, Symbol
|
287
|
+
@mod.to_s.split('::').
|
288
|
+
reject { | name | name.empty?}.
|
289
|
+
inject(Object) { | namespace, name | namespace.const_get(name) }
|
290
|
+
else
|
291
|
+
raise TypeError, "mod_resolve: expected Module, String, Symbol, given #{@mod.class}"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Registers the before, after and around advice methods.
|
296
|
+
def register_advice_methods!
|
297
|
+
scope # force calculation of scope before aliasing methods.
|
298
|
+
|
299
|
+
@mutex.synchronize do
|
300
|
+
return self if @advice_methods_registered
|
301
|
+
|
302
|
+
this = self
|
303
|
+
mod_target.instance_eval do
|
304
|
+
define_method(this.before_meth, &this.advice.before)
|
305
|
+
define_method(this.after_meth, &this.advice.after)
|
306
|
+
define_method(this.around_meth, &this.advice.around)
|
307
|
+
end
|
308
|
+
|
309
|
+
@advice_methods_registered = true
|
310
|
+
end
|
311
|
+
self
|
312
|
+
end
|
313
|
+
|
314
|
+
# Defines the new advised method in the target Module.
|
315
|
+
def define_new_method!
|
316
|
+
advised = self
|
317
|
+
|
318
|
+
advised.mod_target.instance_eval do
|
319
|
+
define_method advised.new_meth do | *args, &block |
|
320
|
+
ar = ActivationRecord.new(advised, self, args, block)
|
321
|
+
|
322
|
+
# Proc to invoke the old method with :before and :after advise hooks.
|
323
|
+
body = Proc.new do
|
324
|
+
self.__send__(advised.before_meth, ar)
|
325
|
+
begin
|
326
|
+
ar.result = self.__send__(advised.old_meth, *ar.args, &ar.block)
|
327
|
+
rescue ::Object => err
|
328
|
+
ar.error = err
|
329
|
+
ensure
|
330
|
+
self.__send__(advised.after_meth, ar)
|
331
|
+
end
|
332
|
+
ar.result
|
333
|
+
end
|
334
|
+
|
335
|
+
# Invoke the :around advice with the body Proc.
|
336
|
+
self.__send__(advised.around_meth, ar, body)
|
337
|
+
|
338
|
+
# Reraise Exception, if occured.
|
339
|
+
raise ar.error if ar.error
|
340
|
+
|
341
|
+
# Return the message result to caller.
|
342
|
+
ar.result
|
343
|
+
end # define_method
|
344
|
+
end # instance_eval
|
345
|
+
|
346
|
+
self
|
347
|
+
end
|
348
|
+
|
349
|
+
if RUBY_VERSION =~ /^1\.8/
|
350
|
+
def scope
|
351
|
+
@scope ||= @mutex.synchronize do
|
352
|
+
this = self
|
353
|
+
mod_target.instance_eval do
|
354
|
+
case
|
355
|
+
when private_instance_methods(false).include?(this.meth.to_s)
|
356
|
+
:private
|
357
|
+
when protected_instance_methods(false).include?(this.meth.to_s)
|
358
|
+
:protected
|
359
|
+
else
|
360
|
+
:public
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
else
|
366
|
+
def scope
|
367
|
+
@scope ||= @mutex.synchronize do
|
368
|
+
this = self
|
369
|
+
mod_target.instance_eval do
|
370
|
+
case
|
371
|
+
when private_instance_methods(false).include?(this.meth)
|
372
|
+
:private
|
373
|
+
when protected_instance_methods(false).include?(this.meth)
|
374
|
+
:protected
|
375
|
+
else
|
376
|
+
:public
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Enables the advice on this method.
|
384
|
+
def enable!
|
385
|
+
@mutex.synchronize do
|
386
|
+
return self if @enabled
|
387
|
+
|
388
|
+
this = self
|
389
|
+
mod_target.instance_eval do
|
390
|
+
case this.scope
|
391
|
+
when :public
|
392
|
+
else
|
393
|
+
public this.meth
|
394
|
+
end
|
395
|
+
|
396
|
+
alias_method this.old_meth, this.meth if
|
397
|
+
! method_defined? this.old_meth
|
398
|
+
|
399
|
+
alias_method this.meth, this.new_meth
|
400
|
+
|
401
|
+
case this.scope
|
402
|
+
when :private
|
403
|
+
private this.meth
|
404
|
+
when :protected
|
405
|
+
protected this.meth
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
enabled!
|
410
|
+
@enabled = true
|
411
|
+
end
|
412
|
+
self
|
413
|
+
end
|
414
|
+
alias :advise! :enable!
|
415
|
+
|
416
|
+
# Disables the advice on this method.
|
417
|
+
def disable!
|
418
|
+
@mutex.synchronize do
|
419
|
+
return self if ! @enabled
|
420
|
+
|
421
|
+
this = self
|
422
|
+
mod_target.instance_eval do
|
423
|
+
if method_defined? this.old_meth
|
424
|
+
alias_method this.meth, this.old_meth
|
425
|
+
|
426
|
+
case this.scope
|
427
|
+
when :private
|
428
|
+
private this.meth
|
429
|
+
when :protected
|
430
|
+
protected this.meth
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
disabled!
|
436
|
+
@enabled = false
|
437
|
+
end
|
438
|
+
|
439
|
+
self
|
440
|
+
end
|
441
|
+
alias :unadvise! :disable!
|
442
|
+
|
443
|
+
# Called when Advised is enabled.
|
444
|
+
# Instances can override this method.
|
445
|
+
def enabled!
|
446
|
+
self
|
447
|
+
end
|
448
|
+
|
449
|
+
# Called when Advised is enabled.
|
450
|
+
# Instances can override this method.
|
451
|
+
def disabled!
|
452
|
+
self
|
453
|
+
end
|
454
|
+
|
455
|
+
end
|
456
|
+
|
457
|
+
|
458
|
+
# Represents the activation record of a method invocation.
|
459
|
+
class ActivationRecord
|
460
|
+
# The Advised method binding object.
|
461
|
+
attr_reader :advised
|
462
|
+
|
463
|
+
# The original message receiver, arguments and block (if given).
|
464
|
+
# Can be modified by the advice blocks.
|
465
|
+
attr_accessor :rcvr, :args, :block
|
466
|
+
|
467
|
+
# The original message return result available in the :around or :after advice blocks.
|
468
|
+
# Value can be changed in the advice blocks to alter the return result.
|
469
|
+
attr_accessor :result
|
470
|
+
|
471
|
+
# Any Exception rescued from the original method.
|
472
|
+
# Usually nil if no exception was raised;
|
473
|
+
# if not nil, Exception is reraised after the :around advice block.
|
474
|
+
# Can be modified by the :after advice block.
|
475
|
+
attr_accessor :error
|
476
|
+
alias :exception :error
|
477
|
+
alias :exception= :error=
|
478
|
+
|
479
|
+
# Arbitrary data accessed by #[], #[]=.
|
480
|
+
# Advice blocks can use this to store arbitrary data.
|
481
|
+
# May be a frozen, empty Hash.
|
482
|
+
attr_accessor :data
|
483
|
+
|
484
|
+
def initialize *args
|
485
|
+
@advised, @rcvr, @args, @block = *args
|
486
|
+
end
|
487
|
+
|
488
|
+
def data
|
489
|
+
@data || EMPTY_Hash
|
490
|
+
end
|
491
|
+
|
492
|
+
def [] key
|
493
|
+
(@data || EMPTY_Hash)[key]
|
494
|
+
end
|
495
|
+
|
496
|
+
def []= key, value
|
497
|
+
(@data ||= { })[key] = value
|
498
|
+
end
|
499
|
+
|
500
|
+
# This methods are delegated to #advised.
|
501
|
+
DELEGATE_TO_ADVISED = [ :advice, :mod, :meth, :kind, :meth_to_s ].freeze
|
502
|
+
eval(DELEGATE_TO_ADVISED.map{|m| "def #{m}; @advised.#{m}; end; "} * "\n")
|
503
|
+
|
504
|
+
# The call stack with CheapAdvice methods filtered out.
|
505
|
+
def caller(offset = 0)
|
506
|
+
::Kernel.caller(offset + 2)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
end
|
511
|
+
|