I have the following code from Ruby 1.9 programming (slightly adapted). I just want my thought process to be accurate.
module Trace def self.included(culprit) #Inject existing methods with tracing code: culprit.instance_methods(false).each do |func| inject(culprit, func) end #Override the singletons method_added to ensure all future methods are injected. def culprit.method_added(meth) unless @trace_calls_internal @trace_calls_internal = true Trace.inject(self, meth) #This will call method_added itself, the condition prevents infinite recursion. @trace_calls_internal = false end end end def self.inject(target, meth) target.instance_eval do #Get the method method_object = instance_method(meth) #Rewrite dat sheet define_method(meth) do |*args, &block| puts "==> Called #{meth} with #{args.inspect}" #the bind to self will put the context back to the class. result = method_object.bind(self).call(*args, &block) puts "<== #{meth} returned #{result.inspect}" result end end end end class Example def one(arg) puts "One called with #{arg}" end end #No tracing for the above. ex1 = Example.new ex1.one("Sup") #Not affected by Trace::inject class Example #extend the class to include tracing. include Trace #calls Trace::inject on existing methods via Trace::included def two(a1, a2) #triggers Example::method_added(two) and by extension Trace::inject a1 + a2 end end ex1.one("Sup again") #Affected by Trace::inject puts ex1.two(5, 4) #Affected by Trace::inject
I'm still trying to bow my head around how this works. I was hoping that someone would be able to confirm my thought process, because I want to make sure that I understand what is happening here. Comments have been added by me. I really believe that my understanding of method binding, singleton classes, and metaprogramming should be a novice at best.
First, Trace :: included is called by any class that includes it. This method does two things, gets a list of existing functions in this class (if any), and overrides their methods with an injection. He then modifies the singleton class of the class that the module includes, and overrides the default method_added method to ensure that every time the method is added past the extra include, it will affect it. This method uses a variable to prevent infinite recursion, because a call to an injection will call the addded method by nature.
Trace :: works as follows: set self to the context that exists in the class definition using instance_eval. Therefore, the region (?) Is changed as it will be within this class definition.
Then we set the object_method instance_method (meth), which will get the original method to be added. Since instance_method does not have an explicit recipient, will it be the default for itself, which will be the same as the context in the class definition?
Then we use define_method to define a method with the same name. Since we are in the context of instance_eval, this is equivalent to defining an instance method and overrides the existing method. our method accepts an arbitrary number of arguments and a block, if one exists.
We add some flash to place our "Trace", then we also call the original method, which we saved in the object-method, as the original is overwritten. This method is unrelated, so we have to bind it to the current context using bind (self) so that it has the same context as the original one? Then we use the call and pass the arguments and the block, storing the return value and returning the return value after printing it.
I really hope that I describe this adequately. Is my description accurate? I especially doubt the bold content and the following line:
method_object.bind(self).call(*args, &block)