3

At runtime, I would like to inject a line of code at the - # <-- Injection point

class A

  def some_method(a, b = 1, *c, d: 1, **e)
    # <-- Injection point
    
    # Code that does stuff
    puts a
    puts b
    puts c
    puts d
    puts e
  end
end

The reason I need this is so that I can use the Ruby Tracepoint Class to extract some run time information about the method signature.

Specifically the default parameter values in b = 1 and d: 1

I have looked at this question Inspect the default value for optional parameter in ruby's method? where a useful answer exists but want to inject the code that they suggest, dynamically.

I already have existing code that can extracts method signatures from ruby classes.

Example

Given this Class, which is defined with lots of methods using various parameterized signatures.

class A
  def d;                                  end
  def e(a);                               end
  def f(a, b = 1);                        end
  def g(a, b = 1, c = 2);                 end
  def h(*a);                              end
  def i(a, b, *c);                        end
  def j(**a);                             end
  def k(a, *b, **c);                      end
  def l(a, *b, **c, &d);                  end
  def m(a:);                              end
  def n(a:, b: 1);                        end
  def p?;                                 end
  def z(a, b = 1, *c, d: 1, **e);         end
end

I can run the following code to extract signature information. Unfortunately, it fails to resolve the default parameter or named values.

class_definition = MethodSignatures.new(A.new)
class_definition.print

Issues are Red

co

David Cruwys
  • 6,262
  • 12
  • 45
  • 91
  • are you able to define those methods in a module? if so then you could simply redefine them in your class as well, and you could call super on them so you could: (i) add the injection and then retain the function that the methods were meanth to perform in the model. Alternatively, are you ok with renaming those methods and then adding the injection in? – BenKoshy Oct 27 '20 at 13:55

2 Answers2

3

The reason I need this is so that I can use the Ruby Tracepoint Class to extract some run time information about the method signature.

Specifically the default parameter values in b = 1 and d: 1

TracePoint allows you to fetch information without patching the method. You just have to set up a :call hook and then call the method:

TracePoint.trace(:call) do |tp|
  tp.parameters.each do |type, name|
    value = tp.binding.local_variable_get(name)
    puts format('%8s: %s = %s', type, name, value.inspect)
  end
end

A.new.some_method(123)

Output: (omitting the method's output)

     req: a = 123
     opt: b = 1
    rest: c = []
     key: d = 1
 keyrest: e = {}
Stefan
  • 109,145
  • 14
  • 143
  • 218
  • Thanks again Stefan, (2 days in a row), this did exactly what I needed. I just added `next unless tp.self.is_a?(A)` to remove the noise. – David Cruwys Oct 28 '20 at 03:12
1

Prior to Ruby v2.0 one would redefine A#some_method after aliasing the original A#some_method.

class A
  def some_method(a, b, c)
    puts a
    puts b
    puts c
  end
end
A.public_send(:alias_method, :old_some_method, :some_method)
A.define_method(:some_method) do |a,b,c|
  puts "a=#{a}, b=#{b}, c=#{c}"
  old_some_method(a,b,c)
end
A.new.some_method(1,2,3)
a=1, b=2, c=3
1
2
3

A better way, however, is to define the new method A#some_method in a module and then Module#prepend that module to the class A. prepend made its debut in Ruby v2.0.

module M
  def some_method(a,b,c)
    puts "a=#{a}, b=#{b}, c=#{c}"
    super
  end
end
A.prepend M
A.new.some_method(1,2,3)
a=1, b=2, c=3
1
2
3

A.prepend M gives the methods of M priority over those of A.

A.ancestors
  #=> [M, A, Object, Kernel, BasicObject]

shows this to be the case and also explains the role of super in the method M#some_method.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • thanks for the information Cary, unfortunately, this will not work for my use-case because do not have access to the code for altering, the idea is to reverse engineer signatures from GEMs and other code that I might not have easy access to. – David Cruwys Oct 28 '20 at 03:14