method_proxy 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +178 -0
- data/lib/method_proxy.rb +218 -0
- data/test/method_proxy_test.rb +126 -0
- metadata +68 -0
data/README.markdown
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
### WHAT'S THIS?
|
2
|
+
|
3
|
+
'method_proxy' gem allows to 'tap' into instance method or class method calls
|
4
|
+
on objects of specific class or on specific classes.
|
5
|
+
|
6
|
+
|
7
|
+
### API
|
8
|
+
|
9
|
+
* `MethodProxy.proxy_instance_method(SomeClass, :some_instance_method, &block)`
|
10
|
+
|
11
|
+
Replaces original instance method SomeClass#some_instance_method with method
|
12
|
+
generated from supplied block. Supplied block should expect following
|
13
|
+
arguments:
|
14
|
+
1) object on which the call is made;
|
15
|
+
2) original method bound to the object on which call is made;
|
16
|
+
*args - arguments of original method call;
|
17
|
+
&block - block passed to the original method call.
|
18
|
+
|
19
|
+
An important thing to remember, is the &block should not use 'return <result>'
|
20
|
+
and should use 'next <result>' construct instead - as 'return' will result in
|
21
|
+
exception:
|
22
|
+
|
23
|
+
LocalJumpError: unexpected return
|
24
|
+
|
25
|
+
|
26
|
+
* `MethodProxy.unproxy_instance_method(SomeClass, :some_instance_method)`
|
27
|
+
|
28
|
+
Restore original instance method SomeClass#some_instance_method.
|
29
|
+
|
30
|
+
|
31
|
+
* `MethodProxy.proxy_class_method(SomeClass, :some_class_method, &block)`
|
32
|
+
|
33
|
+
Replaces original class method with method generated from supplied block.
|
34
|
+
Supplied block should expect following arguments:
|
35
|
+
1) class on which the call is made;
|
36
|
+
2) original method bound to that class;
|
37
|
+
*args - arguments of original method call;
|
38
|
+
&block - block passed to the original method call.
|
39
|
+
|
40
|
+
Just like in case of proxy_instance_method, &block should use 'next <result>'
|
41
|
+
construct instead of 'return <result>' or LocalJumpError will be raised.
|
42
|
+
|
43
|
+
|
44
|
+
* `MethodProxy.unproxy_class_method(SomeClass, :some_class_method)`
|
45
|
+
Restore original class method SomeClass.some_class_method.
|
46
|
+
|
47
|
+
|
48
|
+
* `MethodProxy.classes_with_proxied_instance_methods`
|
49
|
+
|
50
|
+
Returns Array of classes that have at least one instance method tapped into by
|
51
|
+
MethodProxy.
|
52
|
+
|
53
|
+
|
54
|
+
* `MethodProxy.proxied_instance_methods_for(klass)`
|
55
|
+
|
56
|
+
Returns Array of instance method names for class klass that have been altered by
|
57
|
+
MethodProxy.
|
58
|
+
|
59
|
+
|
60
|
+
* `MethodProxy.classes_with_proxied_class_methods`
|
61
|
+
|
62
|
+
Returns Array of classes that have at least one class method tapped into by
|
63
|
+
MethodProxy.
|
64
|
+
|
65
|
+
|
66
|
+
* `MethodProxy.proxied_class_methods_for(klass)`
|
67
|
+
|
68
|
+
Returns Array of class method names for class klass that have been altered by
|
69
|
+
MethodProxy.
|
70
|
+
|
71
|
+
|
72
|
+
### EXAMPLES
|
73
|
+
|
74
|
+
**Example 1.** Non-intrusive debugging. Here's a way to dynamically "inject" call
|
75
|
+
to debugger before an instance method of interest UsersController#create, with
|
76
|
+
the help of MethodProxy:
|
77
|
+
|
78
|
+
require 'method_proxy'
|
79
|
+
require 'ruby-debug'
|
80
|
+
MethodProxy.proxy_instance_method(UsersController, :create) do |users_controller_obj, orig_create_meth, *args|
|
81
|
+
debugger
|
82
|
+
res = orig_create_meth.call *args
|
83
|
+
next res
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
**Example 1b.** Debug on exception:
|
88
|
+
|
89
|
+
require 'method_proxy'
|
90
|
+
require 'ruby-debug'
|
91
|
+
MethodProxy.proxy_instance_method(UsersController, :create) do |users_controller_obj, orig_create_meth, *args|
|
92
|
+
begin
|
93
|
+
res = orig_create_meth.call *args
|
94
|
+
rescue Exception => e
|
95
|
+
debugger
|
96
|
+
end
|
97
|
+
next res
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
**Example 2.** Automatic recording of system actions during QA process. With the help of MethodProxy one can
|
102
|
+
arrange interception of actions of interest (say, those that change database state), and storing of the
|
103
|
+
actions' parameters:
|
104
|
+
|
105
|
+
tbd = {
|
106
|
+
UsersController => [:create, :update, :delete],
|
107
|
+
PostsController => [:create, :update, :delete]
|
108
|
+
}
|
109
|
+
|
110
|
+
tbd.each_pair do |cntrlr, actns|
|
111
|
+
actns.each do |actn|
|
112
|
+
MethodProxy.proxy_instance_method(cntrlr, actn) do |controller, meth, *args|
|
113
|
+
record_hash = Hash.new
|
114
|
+
record_hash[:controller] = controller.class.name.to_sym
|
115
|
+
record_hash[:action] = actn
|
116
|
+
record_hash[:http_meth] = controller.request.method
|
117
|
+
record_hash[:params] = controller.request.params
|
118
|
+
|
119
|
+
store_action_record(record_hash) # store the information - implement according to your needs!
|
120
|
+
|
121
|
+
meth.call *args
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
Later, the resulting "records" can be "replayed" in automated tests.
|
128
|
+
|
129
|
+
|
130
|
+
### CAVEATS
|
131
|
+
|
132
|
+
There is a number of known issues:
|
133
|
+
|
134
|
+
- have to remember to use "next <result>" instead of "return <result>" syntax;
|
135
|
+
- if one needs to tap into methods that are dynamically created, e.g.
|
136
|
+
'find_user_by_id' created on-the-fly with the help of 'method_missing' in
|
137
|
+
Rails, the code should make sure that method has been defined before attempting
|
138
|
+
to tap.
|
139
|
+
|
140
|
+
|
141
|
+
### RELATION TO AOP
|
142
|
+
|
143
|
+
"Tapping" into method calls the way 'method_proxy' does is similar to creating
|
144
|
+
joint points in Aspect-Oriented Programming, and the same task can be
|
145
|
+
accomplished with AOP frameworks like Aquarium. 'method_proxy' however is
|
146
|
+
intended as a simple tool focused on its task - with no attempt to align it
|
147
|
+
with AOP concepts and terminology.
|
148
|
+
|
149
|
+
|
150
|
+
### LICENSE
|
151
|
+
|
152
|
+
Usage, distribution or modification, for both commercial or non-profit purposes,
|
153
|
+
are not limited whatsoever.
|
154
|
+
|
155
|
+
|
156
|
+
### DISCLAIMER
|
157
|
+
|
158
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
159
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
160
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
161
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
162
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
163
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
164
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
165
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
166
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
167
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
168
|
+
|
169
|
+
|
170
|
+
### CREDITS
|
171
|
+
Special thanks for their invaluable feedback, advice, suggestions and fixes to:
|
172
|
+
wtaysom, adehvadeh, shaokun and rainchen.
|
173
|
+
|
174
|
+
|
175
|
+
### PROJECT ON THE WEB
|
176
|
+
|
177
|
+
The project is hosted on GitHub:
|
178
|
+
https://github.com/kudelabs/method_proxy/
|
data/lib/method_proxy.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'thread' if RUBY_VERSION <"1.9"
|
2
|
+
|
3
|
+
class MethodProxyException < Exception
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
class MethodProxy
|
8
|
+
##################################### CLASS VARIABLES #############################################
|
9
|
+
|
10
|
+
@@mx = Mutex.new
|
11
|
+
@@proxied_instance_methods = {}
|
12
|
+
@@proxied_class_methods = {}
|
13
|
+
@@tmp_binding = nil
|
14
|
+
|
15
|
+
#################################### SOME HELPER METHODS ##########################################
|
16
|
+
def self.classes_with_proxied_instance_methods
|
17
|
+
@@mx.synchronize do
|
18
|
+
return @@proxied_instance_methods.keys.collect{|k| Class.const_get(k)}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.proxied_instance_methods_for(klass)
|
23
|
+
raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module)
|
24
|
+
@@mx.synchronize do
|
25
|
+
meth_hash_for_klass = @@proxied_instance_methods[klass.name.to_sym]
|
26
|
+
return [] if !meth_hash_for_klass || meth_hash_for_klass.empty?
|
27
|
+
return meth_hash_for_klass.keys
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def self.classes_with_proxied_class_methods
|
33
|
+
@@mx.synchronize do
|
34
|
+
return @@proxied_class_methods.keys.collect{|k| Class.const_get(k)}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.proxied_class_methods_for(klass)
|
39
|
+
raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module)
|
40
|
+
@@mx.synchronize do
|
41
|
+
meth_hash_for_klass = @@proxied_class_methods[klass.name.to_sym]
|
42
|
+
return [] if !meth_hash_for_klass || meth_hash_for_klass.empty?
|
43
|
+
return meth_hash_for_klass.keys
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
#### WARNING: NON-THREAD-SAFE methods for internal use; generally, they should not be called by ####
|
49
|
+
#### any external code ####
|
50
|
+
def self.register_original_instance_method(klass, meth_name, meth_obj)
|
51
|
+
@@proxied_instance_methods[klass.name.to_sym][meth_name] = meth_obj
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.original_instance_method(klass, meth_name)
|
55
|
+
@@proxied_instance_methods[klass.name.to_sym][meth_name]
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.tmp_binding
|
59
|
+
@@tmp_binding
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
def self.capture_tmp_binding!(bndg)
|
64
|
+
@@tmp_binding = bndg
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.reset_tmp_binding!
|
68
|
+
@@tmp_binding = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
####################################### MAIN STUFF #################################################
|
72
|
+
public
|
73
|
+
|
74
|
+
# "Tap" into instance method calls - subvert original method with the supplied block; preserve
|
75
|
+
# reference to the original method so that it can still be called or restored later on.
|
76
|
+
#
|
77
|
+
# Common idiom:
|
78
|
+
# MethodProxy.proxy_instance_method(SomeClass, :some_instance_method) do |obj, original_instance_meth, *args, &block|
|
79
|
+
#
|
80
|
+
# # do stuff before calling original method
|
81
|
+
# ... ... ...
|
82
|
+
#
|
83
|
+
# # call the original method (already bound to object obj), with supplied arguments
|
84
|
+
# result = original_instance_meth.call(*args, &block)
|
85
|
+
#
|
86
|
+
# # do stuff after calling original method
|
87
|
+
# ... ... ...
|
88
|
+
#
|
89
|
+
# # return the actual return value
|
90
|
+
# result
|
91
|
+
# end
|
92
|
+
def self.proxy_instance_method(klass, meth, &block)
|
93
|
+
raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module)
|
94
|
+
raise "method argument must be a Symbol" unless meth.is_a?(Symbol)
|
95
|
+
raise "must supply block argument" unless block_given?
|
96
|
+
|
97
|
+
proc = Proc.new(&block)
|
98
|
+
|
99
|
+
@@mx.synchronize do
|
100
|
+
@@proxied_instance_methods[klass.name.to_sym] ||= {}
|
101
|
+
if @@proxied_instance_methods[klass.name.to_sym][meth]
|
102
|
+
raise ::MethodProxyException, "The method has already been proxied"
|
103
|
+
end
|
104
|
+
|
105
|
+
klass.class_eval do
|
106
|
+
|
107
|
+
MethodProxy.register_original_instance_method(klass, meth, instance_method(meth))
|
108
|
+
|
109
|
+
undef_method(meth)
|
110
|
+
|
111
|
+
define_method meth do |*args, &blk|
|
112
|
+
ret = proc.call(self, MethodProxy.original_instance_method(klass, meth).bind(self), *args, &blk)
|
113
|
+
return ret
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Restore the original instance method for objects of class klass
|
120
|
+
def self.unproxy_instance_method(klass, meth)
|
121
|
+
raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module)
|
122
|
+
raise "method argument must be a Symbol" unless meth.is_a?(Symbol)
|
123
|
+
|
124
|
+
@@mx.synchronize do
|
125
|
+
return unless @@proxied_instance_methods[klass.name.to_sym][meth].is_a?(UnboundMethod) # pass-through rather than raise
|
126
|
+
proc = @@proxied_instance_methods[klass.name.to_sym][meth]
|
127
|
+
|
128
|
+
klass.class_eval{ define_method(meth, proc) }
|
129
|
+
|
130
|
+
# clean up storage
|
131
|
+
@@proxied_instance_methods[klass.name.to_sym].delete(meth)
|
132
|
+
remaining_proxied_instance_methods = @@proxied_instance_methods[klass.name.to_sym]
|
133
|
+
@@proxied_instance_methods.delete(klass.name.to_sym) if remaining_proxied_instance_methods.empty?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# "Tap" into class method calls - subvert original method with the supplied block; preserve
|
138
|
+
# reference to the original method so that it can still be called or restored later on.
|
139
|
+
#
|
140
|
+
# Common idiom:
|
141
|
+
# MethodProxy.proxy_class_method(SomeClass, :some_class_method) do |klass, original_class_meth, *args, &block|
|
142
|
+
#
|
143
|
+
# # do stuff before calling original method
|
144
|
+
# ... ... ...
|
145
|
+
#
|
146
|
+
# # call original method (already bound to SomeClass), with supplied arguments
|
147
|
+
# result = original_class_meth.call(*args, &block)
|
148
|
+
#
|
149
|
+
# # do stuff after calling original method
|
150
|
+
# ... ... ...
|
151
|
+
#
|
152
|
+
# # return the actual return value
|
153
|
+
# result
|
154
|
+
# end
|
155
|
+
def self.proxy_class_method klass, meth, &block
|
156
|
+
raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module)
|
157
|
+
raise "method argument must be a Symbol" unless meth.is_a?(Symbol)
|
158
|
+
raise "must supply block argument" unless block_given?
|
159
|
+
|
160
|
+
@@mx.synchronize do
|
161
|
+
proc = Proc.new(&block)
|
162
|
+
|
163
|
+
capture_tmp_binding! binding
|
164
|
+
|
165
|
+
@@proxied_class_methods[klass.name.to_sym] ||= {}
|
166
|
+
|
167
|
+
class << klass
|
168
|
+
|
169
|
+
klass, meth, proc = eval "[klass, meth, proc]", MethodProxy.tmp_binding
|
170
|
+
|
171
|
+
self.instance_eval do
|
172
|
+
if @@proxied_class_methods[klass.name.to_sym][meth]
|
173
|
+
raise ::MethodProxyException, "The method has already been proxied"
|
174
|
+
end
|
175
|
+
@@proxied_class_methods[klass.name.to_sym][meth] = instance_method meth
|
176
|
+
end
|
177
|
+
|
178
|
+
remove_method meth
|
179
|
+
|
180
|
+
self.instance_eval do
|
181
|
+
define_method(meth) do |*args, &blk|
|
182
|
+
ret = proc.call(self, @@proxied_class_methods[klass.name.to_sym][meth].bind(self), *args, &blk)
|
183
|
+
return ret
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
reset_tmp_binding!
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Restore the original class method for klass
|
193
|
+
def self.unproxy_class_method klass, meth
|
194
|
+
raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module)
|
195
|
+
raise "method argument must be a Symbol" unless meth.is_a?(Symbol)
|
196
|
+
|
197
|
+
@@mx.synchronize do
|
198
|
+
return unless (class_entries = @@proxied_class_methods[klass.name.to_sym])
|
199
|
+
return unless (orig_unbound_meth = class_entries[meth])
|
200
|
+
|
201
|
+
capture_tmp_binding! binding
|
202
|
+
|
203
|
+
class << klass
|
204
|
+
meth, orig_unbound_meth = eval "[meth, orig_unbound_meth]", MethodProxy.tmp_binding
|
205
|
+
self.instance_eval do
|
206
|
+
define_method meth, orig_unbound_meth
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
reset_tmp_binding!
|
211
|
+
|
212
|
+
# clean up storage
|
213
|
+
@@proxied_class_methods[klass.name.to_sym].delete(meth)
|
214
|
+
remaining_proxied_class_methods = @@proxied_class_methods[klass.name.to_sym]
|
215
|
+
@@proxied_class_methods.delete(klass.name.to_sym) if remaining_proxied_class_methods.empty?
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
class GuineaPig
|
2
|
+
def gp_instance_method a, b
|
3
|
+
return "#{a}-#{b}"
|
4
|
+
end
|
5
|
+
|
6
|
+
def gp_instance_method_w_block a, b, &block
|
7
|
+
interm = "*#{a}*#{b}*"
|
8
|
+
res = yield interm
|
9
|
+
res
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.gp_class_method c, d
|
13
|
+
return "#{c}$#{d}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.gp_class_method_w_block c, d, &block
|
17
|
+
interm = "-=#{c}O#{d}=-"
|
18
|
+
res = yield interm
|
19
|
+
res
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "../lib/method_proxy")
|
24
|
+
require "test/unit"
|
25
|
+
|
26
|
+
class MethodProxyTest < Test::Unit::TestCase
|
27
|
+
def test_proxy_unproxy_instance_method
|
28
|
+
gpig = GuineaPig.new
|
29
|
+
assert_equal "2-3", gpig.gp_instance_method(2, 3)
|
30
|
+
assert [].eql?(MethodProxy.classes_with_proxied_instance_methods)
|
31
|
+
assert_equal [], MethodProxy.proxied_instance_methods_for(GuineaPig)
|
32
|
+
|
33
|
+
MethodProxy.proxy_instance_method(GuineaPig, :gp_instance_method) do |obj, meth, a, b|
|
34
|
+
res = meth.call a, b
|
35
|
+
next "<#{res}>" # within Proc's should be 'next' instead of 'return' -- in order to avoid returning from the caller method
|
36
|
+
end
|
37
|
+
assert_equal "<2-3>", gpig.gp_instance_method(2, 3)
|
38
|
+
assert [GuineaPig].eql?(MethodProxy.classes_with_proxied_instance_methods)
|
39
|
+
assert_equal [:gp_instance_method], MethodProxy.proxied_instance_methods_for(GuineaPig)
|
40
|
+
|
41
|
+
MethodProxy.unproxy_instance_method(GuineaPig, :gp_instance_method)
|
42
|
+
assert_equal "2-3", gpig.gp_instance_method(2, 3)
|
43
|
+
assert [].eql?(MethodProxy.classes_with_proxied_instance_methods)
|
44
|
+
assert_equal [], MethodProxy.proxied_instance_methods_for(GuineaPig)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_proxy_unproxy_instance_method_w_block
|
48
|
+
gpig = GuineaPig.new
|
49
|
+
before_res = gpig.gp_instance_method_w_block(6, 7) do |interm|
|
50
|
+
"#{interm}<-->#{interm}"
|
51
|
+
end
|
52
|
+
assert [].eql?(MethodProxy.classes_with_proxied_instance_methods)
|
53
|
+
assert_equal [], MethodProxy.proxied_instance_methods_for(GuineaPig)
|
54
|
+
assert_equal "*6*7*<-->*6*7*", before_res
|
55
|
+
|
56
|
+
MethodProxy.proxy_instance_method(GuineaPig, :gp_instance_method_w_block) do |obj, meth, a, b, &block|
|
57
|
+
res = meth.call a, b, &block
|
58
|
+
next "!!!__#{res}__!!!" # within Proc's should be 'next' instead of 'return' -- in order to avoid returning from the caller method
|
59
|
+
end
|
60
|
+
|
61
|
+
after_res = gpig.gp_instance_method_w_block(6, 7) do |interm|
|
62
|
+
"#{interm}<-->#{interm}"
|
63
|
+
end
|
64
|
+
|
65
|
+
assert_equal "!!!__*6*7*<-->*6*7*__!!!", after_res
|
66
|
+
assert [GuineaPig].eql?(MethodProxy.classes_with_proxied_instance_methods)
|
67
|
+
assert_equal [:gp_instance_method_w_block], MethodProxy.proxied_instance_methods_for(GuineaPig)
|
68
|
+
|
69
|
+
MethodProxy.unproxy_instance_method(GuineaPig, :gp_instance_method_w_block)
|
70
|
+
after_un_res = gpig.gp_instance_method_w_block(6, 7) do |interm|
|
71
|
+
"#{interm}<-->#{interm}"
|
72
|
+
end
|
73
|
+
assert_equal before_res, after_un_res
|
74
|
+
assert [].eql?(MethodProxy.classes_with_proxied_instance_methods)
|
75
|
+
assert_equal [], MethodProxy.proxied_instance_methods_for(GuineaPig)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_proxy_unproxy_class_method
|
79
|
+
assert_equal "4$5", GuineaPig.gp_class_method(4, 5)
|
80
|
+
assert [].eql?(MethodProxy.classes_with_proxied_class_methods)
|
81
|
+
assert_equal [], MethodProxy.proxied_class_methods_for(GuineaPig)
|
82
|
+
|
83
|
+
MethodProxy.proxy_class_method(GuineaPig, :gp_class_method) do |obj, meth, c, d|
|
84
|
+
res = meth.call c, d
|
85
|
+
next "[#{res}]" # within Proc's should be 'next' instead of 'return' -- in order to avoid returning from the caller method
|
86
|
+
end
|
87
|
+
assert_equal "[4$5]", GuineaPig.gp_class_method(4, 5)
|
88
|
+
assert [GuineaPig].eql?(MethodProxy.classes_with_proxied_class_methods)
|
89
|
+
assert_equal [:gp_class_method], MethodProxy.proxied_class_methods_for(GuineaPig)
|
90
|
+
|
91
|
+
MethodProxy.unproxy_class_method(GuineaPig, :gp_class_method)
|
92
|
+
assert_equal "4$5", GuineaPig.gp_class_method(4, 5)
|
93
|
+
assert [].eql?(MethodProxy.classes_with_proxied_class_methods)
|
94
|
+
assert_equal [], MethodProxy.proxied_class_methods_for(GuineaPig)
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_proxy_unproxy_class_method_w_block
|
98
|
+
before_res = GuineaPig.gp_class_method_w_block(8, 9) do |interm|
|
99
|
+
"#{interm}>--<#{interm}"
|
100
|
+
end
|
101
|
+
assert_equal "-=8O9=->--<-=8O9=-", before_res
|
102
|
+
assert [].eql?(MethodProxy.classes_with_proxied_class_methods)
|
103
|
+
assert_equal [], MethodProxy.proxied_class_methods_for(GuineaPig)
|
104
|
+
|
105
|
+
MethodProxy.proxy_class_method(GuineaPig, :gp_class_method_w_block) do |obj, meth, a, b, &block|
|
106
|
+
res = meth.call a, b, &block
|
107
|
+
next "???__#{res}__???" # within Proc's should be 'next' instead of 'return' -- in order to avoid returning from the caller method
|
108
|
+
end
|
109
|
+
|
110
|
+
after_res = GuineaPig.gp_class_method_w_block(8, 9) do |interm|
|
111
|
+
"#{interm}>--<#{interm}"
|
112
|
+
end
|
113
|
+
|
114
|
+
assert_equal "???__-=8O9=->--<-=8O9=-__???", after_res
|
115
|
+
assert [GuineaPig].eql?(MethodProxy.classes_with_proxied_class_methods)
|
116
|
+
assert_equal [:gp_class_method_w_block], MethodProxy.proxied_class_methods_for(GuineaPig)
|
117
|
+
|
118
|
+
MethodProxy.unproxy_class_method(GuineaPig, :gp_class_method_w_block)
|
119
|
+
after_un_res = GuineaPig.gp_class_method_w_block(8, 9) do |interm|
|
120
|
+
"#{interm}>--<#{interm}"
|
121
|
+
end
|
122
|
+
assert_equal before_res, after_un_res
|
123
|
+
assert [].eql?(MethodProxy.classes_with_proxied_class_methods)
|
124
|
+
assert_equal [], MethodProxy.proxied_class_methods_for(GuineaPig)
|
125
|
+
end
|
126
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: method_proxy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 1
|
9
|
+
version: 0.2.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Jevgenij Solovjov
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-12-01 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: |-
|
22
|
+
Provides convenient means of "taping" into instance or class method calls. The intercepting
|
23
|
+
code is provided with reference to the object, reference to the original method and list of arguments.
|
24
|
+
email: jevgenij@kudelabs.com
|
25
|
+
executables: []
|
26
|
+
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- lib/method_proxy.rb
|
33
|
+
- test/method_proxy_test.rb
|
34
|
+
- README.markdown
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: https://github.com/kudelabs/method_proxy
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
segments:
|
50
|
+
- 0
|
51
|
+
version: "0"
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.3.7
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: Ruby method call interception tool.
|
67
|
+
test_files: []
|
68
|
+
|