Is the current Ruby method called via super?

Inside the method at runtime, is there a way to find out if this method has been called via super in a subclass? For instance.

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

How could I write via_super? or, if necessary, via_super?(:bar) ?

+5
source share
5 answers

The final mix between my other , @mudasobwa and @sawa, supports recursion support:

 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 

The only case that does not work (AFAIK) is if you have indirect recursion in the base class, but I have no idea how to deal with it without having to parse the code.

+1
source

There is probably a better way, but the general idea is that Object#instance_of? limited only by the current class, not the hierarchy:

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


However, note that this does not require an explicit super in the child. If the child does not have such a method and the parent is used, via_super? will return true anyway. I don't think there is a way to catch only the super case, other than checking the stack trace or the code itself.
+4
source

Adding to @ndn's great approach:

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

Here we use Kernel#caller to make sure that the method name is called a name match in the superclass. This approach probably requires some extra tweaking in the absence of a direct descendant ( caller(2) should be changed to a more complex analysis), but you will probably get the point.

UPD thanks to @Stefans comment on another answer updated with unless defined so that it works when both Foo and Fu include SuperDetector .

UPD2 , using ancestors to test super, not direct comparisons.

+3
source

Here's a simpler (almost trivial) approach, but you have to pass both the current class and the method name: (I also changed the method name from via_super? To called_via? )

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

Usage example:

 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" 
+3
source

Edit Improved following Stefan's suggestion.

 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 
+2
source

All Articles