2016-01-12 65 views
5

在運行時的方法中,有沒有辦法知道在子類中是否通過super調用了該方法?例如。是通過超級調用的當前Ruby方法嗎?

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 < Foo 
    def bar 
    super 
    end 
end 

Foo.new.bar # => "nothing special" 
Fu.new.bar # => "super!" 

我怎麼能寫via_super?,或者,如果必要的話,via_super?(:bar)

回答

1

最終混音和答案以及REC之間my other@mudasobwa's@sawa's ursion支持:

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 

是不會工作的唯一情況(據我所知)是,如果你有在基類間接遞歸,但我沒有想法如何與任何短解析的代碼來處理它。

4

有可能是一個更好的辦法,但總的想法是,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的情況。

+0

不幸的是,如果父母和孩子都包含「SuperDetector」,則這不起作用。 – Stefan

+0

@Stefan'除非instance_methods.include?' – mudasobwa

+0

@mudasobwa會阻止孩子使用'超級檢測器' – Stefan

3

的增編,一個優秀的@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確保調用的方法的名稱在超類的名稱相匹配。這種方法可能需要一些額外的調整,如果不是直接的後代(caller(2)應改爲更復雜的分析),但您可能會明白這一點。

UPD感謝@ Stefan的其他答案評論,與unless defined更新,以使其工作時,既FooFuinclude SuperDetector

UPD2使用祖先檢查超級,而不是直接比較。

+0

非常 - 是的。只需使用'Kernel#caller_locations'並調用'location',而不是用正則表達式解析。 – ndn

+0

@ndn你的意思是調用'label',對吧?我沒有看到它的任何優勢,但更新:) – mudasobwa

+0

是的,'標籤'。我的錯。這只是更容易閱讀。 – ndn

2

編輯改進,遵循斯蒂芬的建議。

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 
+0

這兩個'method(mX).owner'調用是否在當前上下文中評估並始終相等? (假設'm0 == m1') – ndn

+1

'm0'和'm1'都是字符串,並且由於它們相等,'call_whatever_func_on_it(m0)'與'call_whatever_func_on_it(m1)'等同。我錯過了嗎? – mudasobwa

+0

我擔心發生遞歸調用時會發生什麼。但我沒有想太多。也許我可以簡化爲'm0 == m1'。 – sawa

3

這裏有一個簡單的(幾乎是微不足道的)方法,但你必須通過這兩個,當前的類和方法名:(我也從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"