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 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/
@@ -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
+