Overview
Ruby's Method#super_method
returns the parent method that would be called when super
is invoked from within the current method. This method provides programmatic access to the same method lookup mechanism that super
uses internally, making it invaluable for debugging complex inheritance hierarchies and understanding method resolution paths.
When Ruby encounters super
within a method, it searches the ancestor chain for the next method with the same name. Method#super_method
exposes this lookup process by returning a Method
object representing that parent method, or nil
if no such method exists.
The method operates on Method
objects created through Object#method
, enabling developers to introspect inheritance chains without executing the methods themselves. This differs fundamentally from the super
keyword, which immediately invokes the parent method and returns its result.
class Animal
def speak
"Generic animal sound"
end
end
class Dog < Animal
def speak
"Woof!"
end
end
dog = Dog.new
current_method = dog.method(:speak)
parent_method = current_method.super_method
puts current_method.owner # => Dog
puts parent_method.owner # => Animal
puts parent_method.call # => "Generic animal sound"
This introspection capability proves particularly useful when working with frameworks like Rails, where deep inheritance chains and module mixins can make method resolution paths difficult to trace manually.
Basic Usage
To use Method#super_method
, first obtain a Method
object using Object#method
, then call super_method
on it. The return value is either another Method
object representing the parent method, or nil
if no parent method exists.
class Vehicle
def start
"Vehicle starting"
end
end
class Car < Vehicle
def start
"Car engine starting"
end
end
car = Car.new
car_start_method = car.method(:start)
vehicle_start_method = car_start_method.super_method
puts car_start_method.call # => "Car engine starting"
puts vehicle_start_method.call # => "Vehicle starting"
puts vehicle_start_method.owner # => Vehicle
The method works seamlessly with multiple inheritance levels, allowing you to traverse entire inheritance chains:
class LivingBeing
def breathe
"Breathing air"
end
end
class Mammal < LivingBeing
def breathe
"Breathing with lungs"
end
end
class Human < Mammal
def breathe
"Conscious breathing"
end
end
human = Human.new
human_method = human.method(:breathe)
mammal_method = human_method.super_method
living_being_method = mammal_method.super_method
puts human_method.call # => "Conscious breathing"
puts mammal_method.call # => "Breathing with lungs"
puts living_being_method.call # => "Breathing air"
When no parent method exists, super_method
returns nil
:
class BaseClass
def unique_method
"Only defined here"
end
end
base = BaseClass.new
method_obj = base.method(:unique_method)
parent_method = method_obj.super_method
puts parent_method.nil? # => true
This behavior mirrors what happens when super
is called from a method with no parent implementation - it raises a NoMethodError
.
Advanced Usage
Method#super_method
supports method chaining, enabling traversal of complete inheritance hierarchies in a single expression. This proves invaluable when debugging deep inheritance chains or understanding complex framework behavior.
class Database
def query(sql)
"Executing: #{sql}"
end
end
class MySQLDatabase < Database
def query(sql)
"MySQL: #{super}"
end
end
class OptimizedMySQLDatabase < MySQLDatabase
def query(sql)
"Optimized #{super}"
end
end
class CachedDatabase < OptimizedMySQLDatabase
def query(sql)
"Cached #{super}"
end
end
db = CachedDatabase.new
current = db.method(:query)
# Walk the entire inheritance chain
methods_chain = []
while current
methods_chain << "#{current.owner}##{current.name}"
current = current.super_method
end
puts methods_chain
# => ["CachedDatabase#query", "OptimizedMySQLDatabase#query",
# "MySQLDatabase#query", "Database#query"]
For complex debugging scenarios, you can create utility methods that provide detailed inheritance analysis:
class Object
def method_ancestry(method_name)
method_chain = []
current_method = method(method_name)
while current_method
method_chain << {
owner: current_method.owner,
source_location: current_method.source_location,
method_object: current_method
}
current_method = current_method.super_method
end
method_chain
end
end
# Usage with Rails-like inheritance
class ApplicationRecord
def save
"Base save logic"
end
end
class User < ApplicationRecord
def save
"User-specific validation: #{super}"
end
end
class AdminUser < User
def save
"Admin privileges: #{super}"
end
end
admin = AdminUser.new
ancestry = admin.method_ancestry(:save)
ancestry.each_with_index do |info, index|
indent = " " * index
location = info[:source_location] ? "#{info[:source_location][0]}:#{info[:source_location][1]}" : "native"
puts "#{indent}#{info[:owner]}#save (#{location})"
end
The method also works with complex module inclusion scenarios, following Ruby's method lookup path exactly as super
would:
module Trackable
def save
puts "Tracking save operation"
super
end
end
module Cacheable
def save
puts "Invalidating cache"
super
end
end
class ActiveRecord
def save
"Persisting to database"
end
end
class Model < ActiveRecord
include Trackable
include Cacheable
def save
puts "Model validation"
super
end
end
model = Model.new
current = model.method(:save)
# Trace the exact path super would take
path = []
while current
path << current.owner
current = current.super_method
end
puts path
# => [Model, Cacheable, Trackable, ActiveRecord]
This demonstrates how Method#super_method
respects Ruby's method lookup order, including the effects of module inclusion.
Common Pitfalls
Several edge cases and gotchas can trap developers working with Method#super_method
, particularly around modules and complex inheritance scenarios.
Nil Return Values
The most common mistake is not handling the case where super_method
returns nil
. This occurs when a method has no parent implementation:
class Standalone
def unique_behavior
"No parent method exists"
end
end
obj = Standalone.new
method_obj = obj.method(:unique_behavior)
# This will raise NoMethodError
begin
parent_method = method_obj.super_method
parent_method.call # => NoMethodError: undefined method `call' for nil:NilClass
rescue NoMethodError => e
puts "Error: #{e.message}"
end
# Safe approach
if parent_method = method_obj.super_method
puts parent_method.call
else
puts "No parent method found"
end
Module vs Class Inheritance Confusion
Method#super_method
follows Ruby's exact method lookup path, which can be counterintuitive when modules are involved:
module Mixin
def greet
"Hello from mixin"
end
end
class Parent
def greet
"Hello from parent"
end
end
class Child < Parent
include Mixin
def greet
"Hello from child: #{super}"
end
end
child = Child.new
child_method = child.method(:greet)
# Many developers expect this to point to Parent, but it points to Mixin
parent_method = child_method.super_method
puts parent_method.owner # => Mixin (not Parent!)
# To reach Parent#greet, you need to go further
grandparent_method = parent_method.super_method
puts grandparent_method.owner # => Parent
Prepended Modules Alter Lookup Order
Prepended modules insert themselves before the class in the lookup chain, which affects super_method
behavior:
module AuthWrapper
def authenticate
puts "Checking authentication"
super
end
end
class User
prepend AuthWrapper # Note: prepend, not include
def authenticate
puts "User authentication logic"
end
end
user = User.new
# The method Ruby finds first is actually from User, not AuthWrapper
auth_method = user.method(:authenticate)
puts auth_method.owner # => User
# But super_method points to the prepended module's version
super_method = auth_method.super_method
puts super_method.nil? # => true (prepended module has no super method)
Aliased Methods Break Chains
Method aliasing can create unexpected breaks in the super_method
chain:
class Original
def process
"Original processing"
end
end
class Enhanced < Original
alias_method :original_process, :process
def process
"Enhanced: #{original_process}"
end
end
enhanced = Enhanced.new
process_method = enhanced.method(:process)
original_method = enhanced.method(:original_process)
# The aliased method doesn't chain to the parent
puts original_method.super_method.nil? # => true
puts process_method.super_method.nil? # => true
Performance Considerations
While Method#super_method
enables powerful debugging capabilities, repeatedly traversing inheritance chains in production code can impact performance:
# Avoid this pattern in performance-critical code
def slow_debug_info(method_name)
current = method(method_name)
chain = []
# This could be expensive for deep inheritance
while current
chain << current.source_location
current = current.super_method
end
chain
end
# Better: Cache results or use only during development
def cached_method_chain(method_name)
@method_chains ||= {}
@method_chains[method_name] ||= slow_debug_info(method_name)
end
Reference
Method Signature
Method | Parameters | Returns | Description |
---|---|---|---|
#super_method |
None | Method or nil |
Returns the parent method that would be called by super , or nil if no parent method exists |
Related Methods
Method | Purpose |
---|---|
Method#owner |
Returns the class or module that defines the method |
Method#source_location |
Returns [filename, line_number] where method was defined |
Method#name |
Returns the method name as a symbol |
Method#call(*args) |
Invokes the method with given arguments |
Method#arity |
Returns number of parameters the method accepts |
UnboundMethod#super_method |
Similar functionality for unbound methods |
Return Values
Condition | Return Value | Example |
---|---|---|
Parent method exists | Method object |
obj.method(:foo).super_method → #<Method: SuperClass#foo> |
No parent method | nil |
obj.method(:unique).super_method → nil |
Method defined in BasicObject | nil |
Root of inheritance chain reached |
Error Conditions
Scenario | Behavior | Solution |
---|---|---|
Calling methods on nil return |
NoMethodError |
Check for nil before calling methods |
Method undefined | N/A (handled by Object#method ) |
Use respond_to? first |
Unbound method context | Different behavior | Use UnboundMethod#super_method |
Usage Patterns
# Safe traversal pattern
def walk_method_chain(method_name)
current = method(method_name)
chain = []
while current
chain << current
current = current.super_method
end
chain
end
# Debugging pattern
def trace_super_calls(method_name)
current = method(method_name)
depth = 0
puts "Method resolution for #{method_name}:"
while current
indent = " " * depth
location = current.source_location
location_str = location ? "#{location[0]}:#{location[1]}" : "native"
puts "#{indent}#{current.owner}##{current.name} (#{location_str})"
current = current.super_method
depth += 1
end
end
# Framework integration pattern
module MethodIntrospection
def method_resolution_path(method_name)
return [] unless respond_to?(method_name)
path = []
current = method(method_name)
while current
path << {
class_name: current.owner.name,
method_name: current.name,
file: current.source_location&.first,
line: current.source_location&.last
}
current = current.super_method
end
path
end
end
Constants and Special Values
Constant | Value | Usage |
---|---|---|
nil |
No parent method | Check before calling methods on return value |
Version Information
- Introduced: Ruby 2.2.0
- Availability: Core Ruby (no gems required)
- Related Features: Works with
super
keyword, method lookup, inheritance
Thread Safety
Method#super_method
is thread-safe as it performs read-only operations on the class hierarchy. The method does not modify any shared state and can be safely called from multiple threads simultaneously.