Class: MethodProxy
- Inherits:
-
Object
- Object
- MethodProxy
- Defined in:
- lib/method_proxy.rb
Constant Summary collapse
- @@mx =
CLASS VARIABLES #############################################
Mutex.new
- @@proxied_instance_methods =
{}
- @@proxied_class_methods =
{}
- @@tmp_binding =
nil
Class Method Summary collapse
- .classes_with_proxied_class_methods ⇒ Object
-
.classes_with_proxied_instance_methods ⇒ Object
SOME HELPER METHODS ##########################################.
- .original_instance_method(klass, meth_name) ⇒ Object
- .proxied_class_methods_for(klass) ⇒ Object
- .proxied_instance_methods_for(klass) ⇒ Object
-
.proxy_class_method(klass, meth, &block) ⇒ Object
“Tap” into class method calls - subvert original method with the supplied block; preserve reference to the original method so that it can still be called or restored later on.
-
.proxy_instance_method(klass, meth, &block) ⇒ Object
“Tap” into instance method calls - subvert original method with the supplied block; preserve reference to the original method so that it can still be called or restored later on.
-
.register_original_instance_method(klass, meth_name, meth_obj) ⇒ Object
WARNING: NON-THREAD-SAFE methods for internal use; generally, they should not be called by #### any external code ####.
- .tmp_binding ⇒ Object
-
.unproxy_class_method(klass, meth) ⇒ Object
Restore the original class method for klass.
-
.unproxy_instance_method(klass, meth) ⇒ Object
Restore the original instance method for objects of class klass.
Class Method Details
.classes_with_proxied_class_methods ⇒ Object
32 33 34 35 36 |
# File 'lib/method_proxy.rb', line 32 def self.classes_with_proxied_class_methods @@mx.synchronize do return @@proxied_class_methods.keys.collect{|k| Class.const_get(k)} end end |
.classes_with_proxied_instance_methods ⇒ Object
SOME HELPER METHODS ##########################################
16 17 18 19 20 |
# File 'lib/method_proxy.rb', line 16 def self.classes_with_proxied_instance_methods @@mx.synchronize do return @@proxied_instance_methods.keys.collect{|k| Class.const_get(k)} end end |
.original_instance_method(klass, meth_name) ⇒ Object
54 55 56 |
# File 'lib/method_proxy.rb', line 54 def self.original_instance_method(klass, meth_name) @@proxied_instance_methods[klass.name.to_sym][meth_name] end |
.proxied_class_methods_for(klass) ⇒ Object
38 39 40 41 42 43 44 45 |
# File 'lib/method_proxy.rb', line 38 def self.proxied_class_methods_for(klass) raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module) @@mx.synchronize do meth_hash_for_klass = @@proxied_class_methods[klass.name.to_sym] return [] if !meth_hash_for_klass || meth_hash_for_klass.empty? return meth_hash_for_klass.keys end end |
.proxied_instance_methods_for(klass) ⇒ Object
22 23 24 25 26 27 28 29 |
# File 'lib/method_proxy.rb', line 22 def self.proxied_instance_methods_for(klass) raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module) @@mx.synchronize do meth_hash_for_klass = @@proxied_instance_methods[klass.name.to_sym] return [] if !meth_hash_for_klass || meth_hash_for_klass.empty? return meth_hash_for_klass.keys end end |
.proxy_class_method(klass, meth, &block) ⇒ Object
“Tap” into class method calls - subvert original method with the supplied block; preserve reference to the original method so that it can still be called or restored later on.
Common idiom: MethodProxy.proxy_class_method(SomeClass, :some_class_method) do |klass, original_class_meth, *args, &block|
# do stuff before calling original method
... ... ...
# call original method (already bound to SomeClass), with supplied arguments
result = original_class_meth.call(*args, &block)
# do stuff after calling original method
... ... ...
# return the actual return value
result
end
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/method_proxy.rb', line 155 def self.proxy_class_method klass, meth, &block raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module) raise "method argument must be a Symbol" unless meth.is_a?(Symbol) raise "must supply block argument" unless block_given? @@mx.synchronize do proc = Proc.new(&block) capture_tmp_binding! binding @@proxied_class_methods[klass.name.to_sym] ||= {} class << klass klass, meth, proc = eval "[klass, meth, proc]", MethodProxy.tmp_binding self.instance_eval do if @@proxied_class_methods[klass.name.to_sym][meth] raise ::MethodProxyException, "The method has already been proxied" end @@proxied_class_methods[klass.name.to_sym][meth] = instance_method meth end remove_method meth self.instance_eval do define_method(meth) do |*args, &blk| ret = proc.call(self, @@proxied_class_methods[klass.name.to_sym][meth].bind(self), *args, &blk) return ret end end end reset_tmp_binding! end end |
.proxy_instance_method(klass, meth, &block) ⇒ Object
“Tap” into instance method calls - subvert original method with the supplied block; preserve reference to the original method so that it can still be called or restored later on.
Common idiom: MethodProxy.proxy_instance_method(SomeClass, :some_instance_method) do |obj, original_instance_meth, *args, &block|
# do stuff before calling original method
... ... ...
# call the original method (already bound to object obj), with supplied arguments
result = original_instance_meth.call(*args, &block)
# do stuff after calling original method
... ... ...
# return the actual return value
result
end
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/method_proxy.rb', line 92 def self.proxy_instance_method(klass, meth, &block) raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module) raise "method argument must be a Symbol" unless meth.is_a?(Symbol) raise "must supply block argument" unless block_given? proc = Proc.new(&block) @@mx.synchronize do @@proxied_instance_methods[klass.name.to_sym] ||= {} if @@proxied_instance_methods[klass.name.to_sym][meth] raise ::MethodProxyException, "The method has already been proxied" end klass.class_eval do MethodProxy.register_original_instance_method(klass, meth, instance_method(meth)) undef_method(meth) define_method meth do |*args, &blk| ret = proc.call(self, MethodProxy.original_instance_method(klass, meth).bind(self), *args, &blk) return ret end end end end |
.register_original_instance_method(klass, meth_name, meth_obj) ⇒ Object
WARNING: NON-THREAD-SAFE methods for internal use; generally, they should not be called by ####
any external code ####
50 51 52 |
# File 'lib/method_proxy.rb', line 50 def self.register_original_instance_method(klass, meth_name, meth_obj) @@proxied_instance_methods[klass.name.to_sym][meth_name] = meth_obj end |
.tmp_binding ⇒ Object
58 59 60 |
# File 'lib/method_proxy.rb', line 58 def self.tmp_binding @@tmp_binding end |
.unproxy_class_method(klass, meth) ⇒ Object
Restore the original class method for klass
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/method_proxy.rb', line 193 def self.unproxy_class_method klass, meth raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module) raise "method argument must be a Symbol" unless meth.is_a?(Symbol) @@mx.synchronize do return unless (class_entries = @@proxied_class_methods[klass.name.to_sym]) return unless (orig_unbound_meth = class_entries[meth]) capture_tmp_binding! binding class << klass meth, orig_unbound_meth = eval "[meth, orig_unbound_meth]", MethodProxy.tmp_binding self.instance_eval do define_method meth, orig_unbound_meth end end reset_tmp_binding! # clean up storage @@proxied_class_methods[klass.name.to_sym].delete(meth) remaining_proxied_class_methods = @@proxied_class_methods[klass.name.to_sym] @@proxied_class_methods.delete(klass.name.to_sym) if remaining_proxied_class_methods.empty? end end |
.unproxy_instance_method(klass, meth) ⇒ Object
Restore the original instance method for objects of class klass
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/method_proxy.rb', line 120 def self.unproxy_instance_method(klass, meth) raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module) raise "method argument must be a Symbol" unless meth.is_a?(Symbol) @@mx.synchronize do return unless @@proxied_instance_methods[klass.name.to_sym][meth].is_a?(UnboundMethod) # pass-through rather than raise proc = @@proxied_instance_methods[klass.name.to_sym][meth] klass.class_eval{ define_method(meth, proc) } # clean up storage @@proxied_instance_methods[klass.name.to_sym].delete(meth) remaining_proxied_instance_methods = @@proxied_instance_methods[klass.name.to_sym] @@proxied_instance_methods.delete(klass.name.to_sym) if remaining_proxied_instance_methods.empty? end end |