Understanding ruby ​​metaprogramming using the addded method to dynamically override instance methods

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) 
+4
source share
2 answers

Trace :: works as follows: set self in the context that exists in the class definition using instance_eval. Therefore, the scope (?) Is changed as it would be within this class definition.

Using an eval instance, you evaluate a block with attachment to an object, which in this case will be the class that includes the module. (I. criminal). Just for clarity, there is a difference between:

 o = Object.new o.instance_eval do puts self end 

and

 class Foo < Object end Foo.instance_eval do puts self end 

Answer: So, you are right in this assumption!


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 receiver, it will be the default for itself, which will be the same as in the context of the class definition?

Yes, you are right in your assumption. Please note that with the request:

 culprit.instance_methods(false) => [:someselector, :someotherselector] 

And the instance invocation method in this context really matches the self.instance_method invocation.


This method is not bound, so we have to bind it to the current context using bind (self), so it has the same context as it was originally?

Yes. When you get a method in the manner defined in the trace module, you get an unbound method object object that can be bound again, as described.


If you want to immerse yourself in Ruby metaprogramming, I recommend the following book: http://pragprog.com/book/ppmetr/metaprogramming-ruby he explains all the detailed details of the Ruby system object, mixins, blocks and everything you can imagine.

+2
source

Your description is in order!

Basically, what the Trace module does is package class methods, so you can run any code before / after the method is executed; transparent to the caller.

The Trace module uses Ruby Hooks, some methods that are called when something happens (the module is turned on, a method is added, etc.). You can find information about this on the Internet, for example:

In this code:

 method_object.bind(self).call(*args, &block) 

As you mentioned, you call an unrelated method using the original context of the method (refer to the original object yourself, since we are inside the instance_eval method) and "oversaturated" any argument or block it.

It is important to note that the method_object is an unrelated method, so you need to bind the context to it, otherwise a NoMethodError exception will be thrown.

+2
source

Source: https://habr.com/ru/post/1483461/


All Articles