【问题标题】:Inject a line of code into an existing method at runtime在运行时将一行代码注入到现有方法中
【发布时间】:2020-10-27 12:53:17
【问题描述】:

在运行时,我想在 -# <-- 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

我需要这个的原因是我可以使用Ruby Tracepoint Class 来提取有关方法签名的一些运行时信息。

具体是b = 1d: 1中的默认参数值

我查看了这个问题Inspect the default value for optional parameter in ruby's method?,其中存在有用的answer,但想动态注入他们建议的代码。

我已经有可以从 ruby​​ 类中提取方法签名的现有代码。

示例

给定这个类,它定义了许多使用各种参数化签名的方法。

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

我可以运行以下代码来提取签名信息。不幸的是,它无法解析default 参数或命名值。

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

问题是红色的

【问题讨论】:

  • 你能在模块中定义这些方法吗?如果是这样,那么您也可以简单地在您的类中重新定义它们,并且您可以对它们调用 super 以便您可以:(i)添加注入,然后保留方法在模型中执行的功能。或者,您可以重命名这些方法然后添加注入吗?

标签: ruby


【解决方案1】:

在 Ruby v2.0 之前,人们会在为原始 A#some_method 起别名后重新定义 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#some_method,然后将该模块Module#prepend 定义到类Aprepend 在 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 赋予M 的方法优先于A 的方法。

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

显示了这种情况,并解释了super 在方法M#some_method 中的作用。

【讨论】:

  • 感谢 Cary 提供的信息,不幸的是,这不适用于我的用例,因为无法访问用于更改的代码,这个想法是从 GEM 和我的其他代码中逆向工程签名可能不容易访问。
【解决方案2】:

我需要它的原因是我可以使用 Ruby Tracepoint 类来提取有关方法签名的一些运行时信息。

具体是b = 1和d中的默认参数值:1

TracePoint 允许您在不修补方法的情况下获取信息。你只需要设置一个:call钩子然后调用方法:

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)

输出:(省略方法的输出)

     req: a = 123
     opt: b = 1
    rest: c = []
     key: d = 1
 keyrest: e = {}

【讨论】:

  • 再次感谢 Stefan,(连续 2 天),这正是我所需要的。我刚刚添加了next unless tp.self.is_a?(A) 来消除噪音。
猜你喜欢
  • 1970-01-01
  • 2021-11-03
  • 2011-09-08
  • 2011-06-17
  • 1970-01-01
  • 1970-01-01
  • 2019-01-15
  • 2011-05-20
  • 1970-01-01
相关资源
最近更新 更多