CrackedRuby logo

CrackedRuby

super_method

A complete guide to Ruby's Method#super_method for debugging inheritance chains and understanding method resolution behavior.

Metaprogramming Method Objects
5.6.5

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_methodnil
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.