Within a method at runtime, is there a way to know if that method has been called via super
in a subclass? E.g.
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)
?
There is probably a better way, but the general idea is that Object#instance_of?
is restricted only to the current class, rather than 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!"
super
in the child. If the child has no such method and the parent's one is used, via_super?
will still return true
. I don't think there is a way to catch only the super
case other than inspecting the stack trace or the code itself.
An addendum to an excellent @ndn 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 name of the method called matches the name in super class. This approach likely requires some additional tuning in case of not direct descendant (caller(2)
should be changed to more sophisticated analysis,) but you probably get the point.
UPD thanks to @Stefan’s comment to the other answer, updated with unless defined
to make it to work when both Foo
and Fu
include SuperDetector
.
UPD2 using ancestors to check for super instead of straight comparison.
Here's a simpler (almost trivial) approach, but you have to pass both, current class and method name: (I've also changed the method name from via_super?
to called_via?
)
module CallDetector
def called_via?(klass, sym)
klass == method(sym).owner
end
end
Example usage:
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"
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With