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