2016-01-12 6 views
5

Innerhalb einer Methode zur Laufzeit gibt es eine Möglichkeit zu wissen, ob diese Methode über super in einer Unterklasse aufgerufen wurde? Z.B.Wird die aktuelle Ruby-Methode über Super aufgerufen?

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!" 

Wie könnte ich schreiben via_super?, oder, wenn nötig, via_super?(:bar)?

Antwort

1

Die ultimative Mischung zwischen my other, @mudasobwa's und @sawa's Antworten sowie rec ursion Unterstützung:

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 

Der einzige Fall, die Arbeit (AFAIK) gewohnt ist, wenn Sie indirekte Rekursion in der Basisklasse haben, aber ich habe keine Ideen, wie es kurz mit etwas zu behandeln, den Code-Parsing.

4

Es gibt wahrscheinlich einen besseren Weg, aber die allgemeine Idee ist, dass Object#instance_of? nur auf die aktuelle Klasse beschränkt ist, nicht als Hierarchie:

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!" 


Beachten Sie jedoch, dass dies nicht ausdrücklich verlangt super im Kind. Wenn das Kind keine solche Methode hat und die des Elternteils verwendet wird, gibt via_super? immer noch true zurück. Ich glaube nicht, dass es eine Möglichkeit gibt, nur den Fall zu erfassen, außer die Stapelverfolgung oder den Code selbst zu untersuchen.

+0

Leider funktioniert das nicht, wenn Eltern und Kind 'SuperDetector' enthalten. – Stefan

+0

@Stefan 'außer instance_methods.include?' – mudasobwa

+0

@mudasobwa das würde verhindern, dass das Kind' SuperDetector' verwendet – Stefan

3

Ein Nachtrag zu einem ausgezeichneten @ndn Ansatz:

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!" 

verwenden wir hier Kernel#caller sicher zu stellen, dass der Name der aufgerufenen Methode die Namen in Superklasse übereinstimmt. Dieser Ansatz erfordert wahrscheinlich einige zusätzliche Optimierung im Fall von nicht direkten Nachkommen (caller(2) sollte zu anspruchsvolleren Analyse geändert werden), aber Sie bekommen wahrscheinlich den Punkt.

UPD dank @ Stefan Kommentar auf die andere Antwort, mit unless defined aktualisiert, um es, wenn beide include SuperDetectorFoo und Fu zu arbeiten.

UPD2 Verwenden von Vorfahren, um nach Super zu suchen, anstatt nach einem direkten Vergleich.

+0

Ziemlich viel - ja. Verwenden Sie einfach 'Kernel # caller_locations' und rufen Sie' location' auf ihnen auf, anstatt mit regex zu parsen. – ndn

+0

@ndn du meinst Call 'Label', richtig? Ich sehe keinen Vorteil davon, aber aktualisiert :) – mudasobwa

+0

Ja, 'label'. Mein Fehler. Es ist einfach leichter zu lesen. – ndn

2

Bearbeiten Verbessert, Stefan Vorschlag folgend.

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

Würden die beiden 'method (mX) .owner'-Aufrufe nicht im aktuellen Kontext ausgewertet und immer gleich sein? (unter der Annahme 'm0 == m1') – ndn

+1

Beide' m0' und 'm1' sind Zeichenketten und da sie gleich sind, wäre' call_whatever_func_on_it (m0) 'identisch mit' call_whatever_func_on_it (m1) '. Vermisse ich etwas? – mudasobwa

+0

Ich hatte Angst, was passiert, wenn es einen rekursiven Anruf gibt. Aber ich habe nicht viel nachgedacht. Vielleicht kann ich es auf 'm0 == m1' vereinfachen. – sawa

3

Hier ist ein einfacher (fast trivial) Ansatz, aber Sie haben beide passieren, aktuelle Klasse und Methodenname: (Ich habe auch den Namen der Methode via_super?-called_via? geändert)

module CallDetector 
    def called_via?(klass, sym) 
    klass == method(sym).owner 
    end 
end 

Beispiel Nutzung:

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" 
Verwandte Themen