CrackedRuby logo

CrackedRuby

method_removed

Overview

Ruby's method_removed is a callback hook that executes automatically when methods are removed from classes or modules using remove_method. This callback belongs to Ruby's broader metaprogramming hook system, providing runtime introspection capabilities for method lifecycle management.

The method_removed hook receives the method name as a symbol parameter and executes in the context of the class or module where the method was removed. Ruby calls this hook immediately after method removal, making it useful for logging, cleanup operations, or triggering dependent behaviors.

class TrackingClass
  def self.method_removed(method_name)
    puts "Method #{method_name} was removed from #{self}"
  end
  
  def example_method
    "Hello World"
  end
end

TrackingClass.remove_method(:example_method)
# Method example_method was removed from TrackingClass

The hook operates differently from method_undefined, which triggers for both remove_method and undef_method calls. The method_removed callback only responds to explicit remove_method invocations, providing more granular control over method removal tracking.

module CallbackComparison
  def self.method_removed(name)
    puts "method_removed: #{name}"
  end
  
  def self.method_undefined(name)
    puts "method_undefined: #{name}"
  end
  
  def test_method; end
end

CallbackComparison.remove_method(:test_method)
# method_removed: test_method
# method_undefined: test_method

CallbackComparison.define_method(:another_method) { }
CallbackComparison.undef_method(:another_method)
# method_undefined: another_method

Ruby's method removal system interacts with inheritance hierarchies in specific ways. The method_removed callback executes on the class or module where the method was originally defined, not necessarily where remove_method was called. This behavior ensures callbacks fire in the correct context even when method removal occurs in subclasses.

Basic Usage

Implementing method_removed requires defining it as a class or module method that accepts a single symbol parameter representing the removed method's name. The callback executes within the same class or module context where the method existed before removal.

class MethodTracker
  @@removed_methods = []
  
  def self.method_removed(method_name)
    @@removed_methods << method_name
    puts "Tracked removal of #{method_name}"
  end
  
  def self.removed_methods
    @@removed_methods.dup
  end
  
  def temporary_method
    "This will be removed"
  end
  
  def another_method
    "This too"
  end
end

MethodTracker.remove_method(:temporary_method)
MethodTracker.remove_method(:another_method)

puts MethodTracker.removed_methods
# [:temporary_method, :another_method]

The callback system works with both instance and class method removal. When removing class methods, the method_removed hook must be defined as a class method to receive the callback. For singleton methods on objects, the callback must exist on the object's singleton class.

class MixedMethodTracking
  def self.method_removed(method_name)
    puts "Class method removed: #{method_name}"
  end
  
  def self.singleton_method_removed(method_name)
    puts "Singleton method removed: #{method_name}"
  end
  
  def self.example_class_method
    "Class method"
  end
  
  def instance_method
    "Instance method"  
  end
end

# Track class method removal
MixedMethodTracking.remove_method(:example_class_method)
# Class method removed: example_class_method

# Instance method removal triggers callback on class
MixedMethodTracking.remove_method(:instance_method)
# Class method removed: instance_method

Module inclusion creates interesting callback behavior patterns. When a module defines method_removed, the callback executes when methods are removed from classes that include the module, but only if the method was originally defined in the module.

module LoggingModule
  def self.method_removed(method_name)
    puts "Method #{method_name} removed from module"
  end
  
  def module_method
    "Defined in module"
  end
end

class IncludingClass
  include LoggingModule
  
  def self.method_removed(method_name)
    puts "Method #{method_name} removed from class"
  end
  
  def class_method
    "Defined in class"
  end
end

# Remove method defined in module
IncludingClass.remove_method(:module_method)
# Method module_method removed from module

# Remove method defined in class
IncludingClass.remove_method(:class_method)  
# Method class_method removed from class

Dynamic method removal often combines with conditional logic to create sophisticated cleanup systems. The callback can examine method names, check object state, or trigger cascading removal operations.

class ConditionalRemoval
  attr_accessor :removal_policy
  
  def self.method_removed(method_name)
    if @removal_policy == :cascade
      related_methods = instance_methods.select { |m| m.to_s.include?(method_name.to_s) }
      related_methods.each { |m| remove_method(m) if method_defined?(m) }
    end
  end
  
  def user_create; end
  def user_update; end  
  def user_delete; end
  def user_validate; end
end

ConditionalRemoval.removal_policy = :cascade
ConditionalRemoval.remove_method(:user_create)
# Removes user_update, user_delete, user_validate automatically

Advanced Usage

Complex metaprogramming scenarios often require sophisticated method_removed implementations that coordinate with other callback hooks and manage intricate object relationships. Advanced usage patterns involve method removal cascades, proxy object cleanup, and dynamic interface maintenance.

class AdvancedMethodManager
  @@method_registry = Hash.new { |h, k| h[k] = [] }
  @@dependency_graph = Hash.new { |h, k| h[k] = Set.new }
  @@method_proxies = {}
  
  def self.register_method_dependency(source, target)
    @@dependency_graph[source] << target
  end
  
  def self.method_removed(method_name)
    cleanup_dependencies(method_name)
    remove_method_proxies(method_name)
    update_method_registry(method_name)
    trigger_dependent_removals(method_name)
  end
  
  private
  
  def self.cleanup_dependencies(method_name)
    @@dependency_graph[method_name].each do |dependent|
      if method_defined?(dependent)
        puts "Cascade removing dependent method: #{dependent}"
        remove_method(dependent)
      end
    end
    @@dependency_graph.delete(method_name)
  end
  
  def self.remove_method_proxies(method_name)
    if @@method_proxies[method_name]
      @@method_proxies[method_name].each(&:invalidate!)
      @@method_proxies.delete(method_name)
    end
  end
  
  def self.update_method_registry(method_name)
    @@method_registry.each { |key, methods| methods.delete(method_name) }
  end
  
  def self.trigger_dependent_removals(method_name)
    @@dependency_graph.each do |source, targets|
      if targets.include?(method_name) && method_defined?(source)
        puts "Removing #{source} due to dependency on #{method_name}"
        remove_method(source)
      end
    end
  end
end

Metaprogramming frameworks often combine method_removed with method generation systems to maintain consistent interfaces. This pattern ensures that generated methods are properly tracked and cleaned up when removed.

module DynamicInterface
  def self.included(base)
    base.extend(ClassMethods)
    base.instance_variable_set(:@generated_methods, Set.new)
  end
  
  module ClassMethods
    def generate_accessor_methods(attribute_name)
      getter_name = attribute_name.to_sym
      setter_name = "#{attribute_name}=".to_sym
      
      define_method(getter_name) do
        instance_variable_get("@#{attribute_name}")
      end
      
      define_method(setter_name) do |value|
        instance_variable_set("@#{attribute_name}", value)
      end
      
      @generated_methods << getter_name
      @generated_methods << setter_name
    end
    
    def method_removed(method_name)
      if @generated_methods&.include?(method_name)
        @generated_methods.delete(method_name)
        cleanup_related_methods(method_name)
      end
    end
    
    private
    
    def cleanup_related_methods(method_name)
      method_string = method_name.to_s
      if method_string.end_with?('=')
        # Removing setter, check for getter
        getter_name = method_string.chomp('=').to_sym
        if @generated_methods.include?(getter_name)
          remove_method(getter_name)
        end
      else
        # Removing getter, check for setter
        setter_name = "#{method_string}=".to_sym
        if @generated_methods.include?(setter_name)
          remove_method(setter_name)
        end
      end
    end
  end
end

class ConfigurableClass
  include DynamicInterface
  
  generate_accessor_methods(:username)
  generate_accessor_methods(:email)
end

# Removing username getter also removes username= setter
ConfigurableClass.remove_method(:username)

Advanced callback systems often implement method removal observation patterns that notify multiple observers about method lifecycle events. This approach supports loosely coupled architectures where different components need to respond to method changes.

class ObservableMethodManager
  @@observers = Hash.new { |h, k| h[k] = [] }
  
  def self.add_removal_observer(method_name, observer)
    @@observers[method_name] << observer
  end
  
  def self.method_removed(method_name)
    notify_observers(method_name)
    cleanup_observer_registry(method_name)
  end
  
  private
  
  def self.notify_observers(method_name)
    @@observers[method_name].each do |observer|
      begin
        observer.method_removed_notification(method_name, self)
      rescue => e
        warn "Observer notification failed for #{method_name}: #{e.message}"
      end
    end
  end
  
  def self.cleanup_observer_registry(method_name)
    @@observers.delete(method_name)
  end
end

class RemovalLogger
  def method_removed_notification(method_name, source_class)
    File.open('method_removals.log', 'a') do |file|
      file.puts "[#{Time.now}] Method #{method_name} removed from #{source_class}"
    end
  end
end

class RemovalEmailer
  def method_removed_notification(method_name, source_class)
    # Send email notification about method removal
    puts "EMAIL: Critical method #{method_name} removed from #{source_class}"
  end
end

logger = RemovalLogger.new
emailer = RemovalEmailer.new

ObservableMethodManager.add_removal_observer(:critical_method, logger)
ObservableMethodManager.add_removal_observer(:critical_method, emailer)

Common Pitfalls

Method removal callbacks create several subtle behavioral patterns that frequently cause confusion in Ruby applications. Understanding these pitfalls prevents debugging difficulties and ensures predictable metaprogramming behavior.

The inheritance chain affects where method_removed callbacks execute. Ruby calls the callback on the class where the method was originally defined, not where remove_method was invoked. This behavior surprises developers who expect callbacks to fire in the calling context.

class ParentClass
  def self.method_removed(method_name)
    puts "Method removed from ParentClass: #{method_name}"
  end
  
  def inherited_method
    "From parent"
  end
end

class ChildClass < ParentClass
  def self.method_removed(method_name)  
    puts "Method removed from ChildClass: #{method_name}"
  end
  
  def child_method
    "From child"
  end
end

# This triggers callback on ParentClass, not ChildClass
ChildClass.remove_method(:inherited_method)
# Method removed from ParentClass: inherited_method

# This triggers callback on ChildClass
ChildClass.remove_method(:child_method)
# Method removed from ChildClass: child_method

Module method removal creates particularly confusing callback behavior. When removing methods that originated from included modules, the callback executes on the module, not the including class. This pattern often breaks expected callback chains.

module ProblematicModule
  def self.method_removed(method_name)
    puts "Module callback: #{method_name}"
  end
  
  def module_method
    "From module"
  end
end

class IncludingClass
  include ProblematicModule
  
  def self.method_removed(method_name)
    puts "Class callback: #{method_name}"
    # This won't fire for module methods!
  end
end

# Only module callback fires, not class callback
IncludingClass.remove_method(:module_method)
# Module callback: module_method

Recursive method removal within callbacks creates infinite loops or stack overflow errors. This occurs when the method_removed callback itself calls remove_method, either directly or through cascading removal logic.

class RecursiveProblem
  def self.method_removed(method_name)
    # DON'T DO THIS - creates infinite recursion
    if method_name.to_s.include?('temp')
      remove_method(:another_temp_method) if method_defined?(:another_temp_method)
    end
  end
  
  def temp_method_one; end
  def another_temp_method; end
end

# This creates infinite recursion
# RecursiveProblem.remove_method(:temp_method_one)

# Safe alternative using guard conditions
class SafeRecursion
  @@removal_in_progress = Set.new
  
  def self.method_removed(method_name)
    return if @@removal_in_progress.include?(method_name)
    
    @@removal_in_progress << method_name
    
    if method_name.to_s.include?('temp')
      related_methods = instance_methods.select { |m| m.to_s.include?('temp') && m != method_name }
      related_methods.each do |m|
        remove_method(m) if method_defined?(m)
      end
    end
    
    @@removal_in_progress.delete(method_name)
  end
end

Exception handling within method_removed callbacks requires careful consideration. Unhandled exceptions in callbacks can interrupt the method removal process and leave objects in inconsistent states.

class ExceptionProneCallback
  def self.method_removed(method_name)
    # Dangerous - unhandled exceptions break removal
    raise "Cannot remove #{method_name}!" if method_name == :protected_method
  end
  
  def regular_method; end
  def protected_method; end
end

# Safe exception handling pattern
class SafeExceptionHandling
  def self.method_removed(method_name)
    begin
      validate_removal(method_name)
      cleanup_resources(method_name)
      log_removal(method_name)
    rescue => e
      # Log error but don't interrupt removal
      warn "Error in method_removed callback: #{e.message}"
      # Ensure cleanup still occurs
      force_cleanup(method_name) rescue nil
    end
  end
  
  private
  
  def self.validate_removal(method_name)
    # Validation logic that might raise
  end
  
  def self.cleanup_resources(method_name)
    # Resource cleanup
  end
  
  def self.log_removal(method_name)
    # Logging that might fail
  end
  
  def self.force_cleanup(method_name)
    # Emergency cleanup
  end
end

Timing issues emerge when method_removed callbacks modify class state that other threads might be accessing. The callback executes immediately after method removal, but before Ruby releases internal locks, creating race conditions in multithreaded applications.

class ThreadingPitfall
  @@method_count = 0
  @@method_mutex = Mutex.new
  
  def self.method_removed(method_name)
    # Race condition without synchronization
    @@method_count -= 1
    puts "Method count: #{@@method_count}"
  end
  
  # Safe alternative
  def self.thread_safe_method_removed(method_name)
    @@method_mutex.synchronize do
      @@method_count -= 1
      puts "Thread-safe method count: #{@@method_count}"
    end
  end
end

Reference

Method Removal Callbacks

Method Context Parameters Purpose
method_removed(name) Class/Module name (Symbol) Called when remove_method removes a method
method_undefined(name) Class/Module name (Symbol) Called when remove_method or undef_method removes/undefines a method
singleton_method_removed(name) Object name (Symbol) Called when singleton methods are removed

Method Removal Operations

Operation Effect Triggers Callback
remove_method(:name) Removes method definition from current class method_removed, method_undefined
undef_method(:name) Prevents method calls, hides inherited methods method_undefined only
class_eval { remove_method(:name) } Same as remove_method in class context method_removed, method_undefined

Callback Execution Context

Scenario Callback Location Notes
Instance method removal Defining class Callback fires where method was originally defined
Class method removal Metaclass Use def self.method_removed
Module method removal Module itself Not the including class
Inherited method removal Parent class Child class callbacks don't fire
Singleton method removal Singleton class Use singleton_method_removed

Common Method States

State Check Method Returns
Method exists method_defined?(:name) true/false
Private method exists private_method_defined?(:name) true/false
Public method exists public_method_defined?(:name) true/false
Method callable respond_to?(:name) true/false

Callback Registration Patterns

# Class-level callback
class MyClass
  def self.method_removed(name)
    # Handle removal
  end
end

# Module-level callback  
module MyModule
  def self.method_removed(name)
    # Handle removal
  end
end

# Instance-specific callback (rare)
obj = Object.new
def obj.singleton_method_removed(name)
  # Handle singleton removal
end

Thread Safety Considerations

Pattern Thread Safe Recommendation
Reading instance variables No Use mutex or atomic operations
Writing to class variables No Synchronize with Mutex
Calling other methods Depends Avoid state-changing method calls
Logging operations Usually Use thread-safe logging libraries

Exception Handling

def self.method_removed(method_name)
  begin
    # Callback logic
  rescue StandardError => e
    # Log error but don't re-raise
    logger.error("method_removed failed: #{e.message}")
  ensure
    # Cleanup that must occur
  end
end

Performance Characteristics

Operation Cost Impact
Simple callback Low Minimal overhead
Complex callback logic Medium Proportional to callback complexity
Exception in callback High Disrupts removal process
Recursive removal Very High Can cause stack overflow

Integration Patterns

# Observer pattern
def self.method_removed(name)
  notify_observers(:method_removed, name)
end

# Logging pattern
def self.method_removed(name)
  logger.info("Method removed: #{name} from #{self}")
end

# Cleanup pattern
def self.method_removed(name)
  cleanup_method_resources(name)
  update_method_registry(name)
end

# Cascade pattern (with guards)
def self.method_removed(name)
  return if @removing_methods
  @removing_methods = true
  remove_dependent_methods(name)
ensure
  @removing_methods = false
end