Note that there are some explanatory texts on larger screens.

plurals
  1. POUnderstanding ruby metaprogramming using method_added to overwrite instance methods dynamically
    text
    copied!<p>I have the following code from Programming Ruby 1.9 (slightly adapted) I just want to ensure my thought process is accurate</p> <pre><code>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, &amp;block| puts "==&gt; Called #{meth} with #{args.inspect}" #the bind to self will put the context back to the class. result = method_object.bind(self).call(*args, &amp;block) puts "&lt;== #{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 </code></pre> <p>I'm still trying to wrap my head around how this works. I was hoping if someone could confirm my thought process as I want to make sure I understand what is going on here. The comments were added by myself.I really consider my understanding of method binding, singleton classes, and meta-programming to be novice at best.</p> <p>Firstly, Trace::included is called by any class that includes it. This method does two things, gets a list of existing functions in that class (if any) and overrides their methods using inject. Then it modifies the singleton class of the class that included the module and overrides the default method_added method to ensure every time a method is added past the additional include inject will affect it. This method uses a variable to prevent infinite recursion because the call to inject will evoke method_added by its nature.</p> <p>Trace::works as follows: set self to the context that exists within the class definition using instance_eval. Therefore the <strong>scope(?)</strong> is modified to how it would be within that class definition. </p> <p>Then we set method_object to instance_method(meth) which will get the original method that will added. Since <strong>instance_method does not have an explicit receiver, it will default to self which will be the same as the context of being within the class definition?</strong></p> <p>Then we use define_method to define a method with the same name. Because we are in the context of instance_eval, this is equivalent to defining an instance method and will override the existing method. our method accepts an arbitrary number of arguments and a block if one exists.</p> <p>We add some flare to place our "Tracing" then we also call the original method that we stored in method_object, as the original is being overwritten. This method is <strong>unbound, so we must bind it to the current context using bind(self) so it has the same context as it would originally?</strong> we then use call and pass through the arguments and the block, storing its return value, and returning its return value after printing it. </p> <hr> <p>I really hope that I am describing this adequately. Is my description accurate? I am particularly uncertain about the bolded content and the following line:</p> <pre><code>method_object.bind(self).call(*args, &amp;block) </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload