1
votes

I'm trying to write on a module where it can be used on every class regardless of its content:

The module should perform the following:

  • print the name method and its parameter whenever a method is called.

  • print the return value of that method.

As an example I got:

class A
  extend Loginator

  def add(a, b)
    a + b
  end

  def sub(a, b)
    a - b
  end
  logify_me #this where the "logging happens!"
end


a = A.new
a.add(3, 5)
a.sub(7, 4)

output

Methode add(3, 5) called
returns 8
Methode sub(7, 4) called
returns 3

I don't know where to start from. I already read the following links :

So what i did is the following but im kinda stuck:

First try

module Loginator
  def logify_me(name)
    attr_reader name
    define_method("#{name}=") do |val|
      puts "#{name}=#{val}"
      instance_variable_set("@#{name}", val)
    end
  end
end

class Example
  extend Loginator
  logify_me :an_attribute
end

e = Example.new
e.an_attribute = 12
p e.an_attribute 

problem with this code is that first i have to strictly write logify_me for each method and wont print anything if i wrote logify_me alone

Second try

module Loginator
  def logify_me
    self.instance_methods.each do |method|
      define_method(method) do |*args|
        puts "Method #{method}(#{args.join(', ')})"
        puts "returns #{args}"
        args#problem this return the args not the results of each method?!
      end
    end
  end
end

Note that i could use TracePoint.trace(:call) but its not what desired :).

Thanks for the user @pedrosfdcarneiro for pointing out the wrapper module solution and providing this ref

1
Welcome to SO. Your question isn't asked well. You gave us requirements, but didn't show us any attempt to solve the problem yourself. SO isn't a code-writing service, we help you debug code you wrote. Please see "How to Ask", "Stack Overflow question checklist" and "MCVE" and all their linked pages.the Tin Man
@theTinMan added where I'm currently at. so if you are planning to be helpful this is the right time :)kkmm

1 Answers

0
votes

You can achieve that by defining a new wrapper method for each public instance method of the class that performs the log logic that you need.

module Loginator
  def logify_me
    self.public_instance_methods.each do |method|
      proxy = Module.new do
        define_method(method) do |*args|
          puts "Method #{method}(#{args.join(', ')}) called"
          # added join to match the exact desired output

          value = super *args

          puts "returns #{value}"

          value
        end
      end
      self.prepend proxy
    end
  end
end

class A
  extend Loginator

  def add(a, b)
    a + b
  end

  def sub(a, b)
    a - b
  end

  logify_me
end

Which yields the following output:

>> A.new.sub(1,2)
Method 'sub' called with args '[1, 2]'
returns -1
=> -1

>> A.new.add(4,7)
Method 'add' called with args '[4, 7]'
returns 11
=> 11

Explanation for the future :)

  • define_method(method) :

    Defines an instance method in the receiver. The method parameter can be a Proc, a Method, or an UnboundMethod object. If a block is specified, it is used as the method body. This block is evaluated using instance_eval ref. can be found here

  • prepend

    prepend will insert the module at the bottom of the chain, even before the class itself.fourth ref on my post

Section to Edit by @pedrosfdcarneiro

please, add some brief explanation about the second inner module proxy = Module.new do end and why it's important for the return value and the usage of the super.

plus why did you preferred to use the public_instance_methods over instance_methods.

please kindly add some explanation about it because first its a bit unclear to me and secondly for the other noobs out there. thanks in advance :)

This answer was heavily based on this fantastic answer: https://stackoverflow.com/a/23898539/1781212.