当前的Ruby方法是通过super调用的吗?
在运行时的方法中,有没有办法知道该方法是否已通过子类中的super
调用? 例如
module SuperDetector def via_super? # what goes here? end end class Foo include SuperDetector def bar via_super? ? 'super!' : 'nothing special' end end class Fu "nothing special" Fu.new.bar # => "super!"
我怎么能写via_super?
,或者,如有必要, via_super?(:bar)
?
我的另一个 , @ mudasobwa和@sawa 的答案以及递归支持之间的最终组合:
module SuperDetector def self.included(clazz) unless clazz.instance_methods.include?(:via_super?) clazz.send(:define_method, :via_super?) do first_caller_location = caller_locations.first calling_method = first_caller_location.base_label same_origin = ->(other_location) do first_caller_location.lineno == other_location.lineno and first_caller_location.absolute_path == other_location.absolute_path end location_changed = false same_name_stack = caller_locations.take_while do |location| should_take = location.base_label == calling_method and !location_changed location_changed = !same_origin.call(location) should_take end self.kind_of?(clazz) and !same_origin.call(same_name_stack.last) end end end end
唯一不起作用的情况(AFAIK)是如果你在基类中有间接递归,但我没有想法如何处理它与解析代码之间的任何事情。
可能有更好的方法,但一般的想法是Object#instance_of?
仅限于当前类,而不是层次结构:
module SuperDetector def self.included(clazz) clazz.send(:define_method, :via_super?) do !self.instance_of?(clazz) end end end class Foo include SuperDetector def bar via_super? ? 'super!' : 'nothing special' end end class Fu < Foo def bar super end end Foo.new.bar # => "nothing special" Fu.new.bar # => "super!"
但是,请注意,这不需要在孩子中显式super
。 如果孩子没有这样的方法并且使用了父母的方法,那么via_super?
仍然会返回true
。 除了检查堆栈跟踪或代码本身之外,我认为没有办法只捕获super
大小写。
一个优秀的@ndn方法的附录:
module SuperDetector def self.included(clazz) clazz.send(:define_method, :via_super?) do self.ancestors[1..-1].include?(clazz) && caller.take(2).map { |m| m[/(?<=`).*?(?=')/] }.reduce(&:==) # or, as by @ndn: caller_locations.take(2).map(&:label).reduce(&:==) end unless clazz.instance_methods.include? :via_super? end end class Foo include SuperDetector def bar via_super? ? 'super!' : 'nothing special' end end class Fu < Foo def bar super end end puts Foo.new.bar # => "nothing special" puts Fu.new.bar # => "super!"
这里我们使用Kernel#caller
来确保所Kernel#caller
方法的名称与超类中的名称匹配。 这种方法可能需要一些额外的调整,以防不是直接后代( caller(2)
应该更改为更复杂的分析),但你可能得到了重点。
UPD感谢include SuperDetector
对其他答案的评论,更新时unless defined
为在Foo
和Fu
include SuperDetector
时使其工作。
UPD2使用祖先检查超级而不是直接比较。
这是一个更简单(几乎是微不足道)的方法,但你必须传递当前类和方法名称:(我还将方法名称从via_super?
更改为called_via?
)
module CallDetector def called_via?(klass, sym) klass == method(sym).owner end end
用法示例:
class A include CallDetector def foo called_via?(A, :foo) ? 'nothing special' : 'super!' end end class B < A def foo super end end class C < A end A.new.foo # => "nothing special" B.new.foo # => "super!" C.new.foo # => "nothing special"
编辑改进,遵循Stefan的建议。
module SuperDetector def via_super? m0, m1 = caller_locations[0].base_label, caller_locations[1]&.base_label m0 == m1 and (method(m0).owner rescue nil) == (method(m1).owner rescue nil) end end