关于TracePoint!
为了扩展我原来的问题,我有一个超类,我想多次扩展:
class BaseClass
end
我希望该类的所有扩展版本都具有FRIENDLY_NAME 常量。当在子类中明确指定时,这将是常量的值。例如:
class Subclass1 < BaseClass
FRIENDLY_NAME = 'Bob seems like a friendly name!'
end
因此:
pry(main)> Subclass1::FRIENDLY_NAME
=> "Bob seems like a friendly name!"
但是,也许我有另一个没有定义常量的子类。例如:
class Subclass2 < BaseClass
end
这就是我在没有常量的情况下得到的(显然):
pry(main)> Subclass2::FRIENDLY_NAME
NameError: uninitialized constant Subclass2::FRIENDLY_NAME
from /bundle/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/active_support.rb:79:in `block in load_missing_constant'
Caused by NameError: uninitialized constant Subclass2::FRIENDLY_NAME
from /bundle/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/active_support.rb:60:in `block in load_missing_constant'
如何添加该常量?您可以将self.inherited 添加到超类。每次扩展超类时都会调用此方法。 Module 上还有一个名为 const_set 的方法,所以我可以像这样自动设置 child 上的常量:
class BaseClass
def self.inherited(child)
child.const_set(:FRIENDLY_NAME, child.name)
end
end
这适用于Subclass2:
pry(main)> Subclass2::FRIENDLY_NAME
=> "Subclass2"
但是,它会为 Subclass1 产生丑陋的警告
(pry):10: warning: already initialized constant Subclass1::FRIENDLY_NAME
(pry):3: warning: previous definition of FRIENDLY_NAME was here
最初我尝试使用const_defined? 方法检查FRIENDLY_NAME 常量是否已经定义。问题是,self.inherited 方法被调用之前在子类中定义常量。基本上,只要 Ruby 看到 < BaseClass,它就会运行 self.inherited。
最终我找到了this SO question about how to execute code after a class has been defined。答案是使用TracePoint。
TracePoint 是一个允许我们在代码中处理事件的类。这些可能是在执行一行代码时、调用方法时、引发错误时等等。我们关心的是:end 事件,它在类或模块定义完成时发生。
知道了这一点,我们可以像这样更新我们的 self.inherited 方法:
class BaseClass
def self.inherited(child)
TracePoint.trace(:end) do |t|
if child == t.self
unless child.const_defined?(:FRIENDLY_NAME)
child.const_set(:FRIENDLY_NAME, child.code)
end
end
end
end
end
现在,Subclass1 和 Subclass2 都有一个 FRIENDLY_NAME 常量,但都不会产生警告!