Cross-logging in Ruby

I am trying to add a log to a method from the outside (Aspect-oriented-style)

class A def test puts "I'm Doing something..." end end class A # with logging! alias_method :test_orig, :test def test puts "Log Message!" test_orig end end a = A.new a.test 

The above works fine, except that if I ever need to execute the method alias again, it goes into an infinite loop. I want something more than super, where I could extend it as many times as I needed, and each extension with the alias of its parent.

+4
source share
2 answers

Another alternative is to use unrelated methods:

 class A original_test = instance_method(:test) define_method(:test) do puts "Log Message!" original_test.bind(self).call end end class A original_test = instance_method(:test) counter = 0 define_method(:test) do counter += 1 puts "Counter = #{counter}" original_test.bind(self).call end end irb> A.new.test Counter = 1 Log Message! #=> #.... irb> A.new.test Counter = 2 Log Message! #=> #..... 

This has the advantage that it does not pollute the namespace with additional method names and is fairly easy to abstract if you want to create an add_logging class add_logging or whatever you have.

 class Module def add_logging(*method_names) method_names.each do |method_name| original_method = instance_method(method_name) define_method(method_name) do |*args,&blk| puts "logging #{method_name}" original_method.bind(self).call(*args,&blk) end end end end class A add_logging :test end 

Or, if you want to be able to make a bunch of aspects without a lot of boiler plates, you can write a method that writes methods for adding an aspect!

 class Module def self.define_aspect(aspect_name, &definition) define_method(:"add_#{aspect_name}") do |*method_names| method_names.each do |method_name| original_method = instance_method(method_name) define_method(method_name, &(definition[method_name, original_method])) end end end # make an add_logging method define_aspect :logging do |method_name, original_method| lambda do |*args, &blk| puts "Logging #{method_name}" original_method.bind(self).call(*args, &blk) end end # make an add_counting method global_counter = 0 define_aspect :counting do |method_name, original_method| local_counter = 0 lambda do |*args, &blk| global_counter += 1 local_counter += 1 puts "Counters: global@ #{global_counter}, local@ #{local_counter}" original_method.bind(self).call(*args, &blk) end end end class A def test puts "I'm Doing something..." end def test1 puts "I'm Doing something once..." end def test2 puts "I'm Doing something twice..." puts "I'm Doing something twice..." end def test3 puts "I'm Doing something thrice..." puts "I'm Doing something thrice..." puts "I'm Doing something thrice..." end def other_tests puts "I'm Doing something else..." end add_logging :test, :test2, :test3 add_counting :other_tests, :test1, :test3 end 
+9
source

First choice: subclass instead of overriding:

 class AWithLogging < A\ def test puts "Log Message!" super end end 

Second option: name your original methods more carefully:

 class A # with logging! alias_method :test_without_logging, :test def test puts "Log Message!" test_without_logging end end 

Then, in another aspect, another name is used orig:

 class A # with frobnication! alias_method :test_without_frobnication, :test def test Frobnitz.frobnicate(self) test_without_frobnication end end 
+2
source

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


All Articles