CrackedRuby logo

CrackedRuby

respond_to? and respond_to_missing?

Overview

Ruby's respond_to? method checks whether an object can respond to a particular method name, forming the foundation of duck typing and dynamic programming patterns. The method returns true when the object defines the specified method, either explicitly or through method_missing handling.

The respond_to_missing? method works alongside method_missing to handle cases where methods exist dynamically but aren't explicitly defined. When Ruby calls respond_to? on an object, it first checks for explicit method definitions, then calls respond_to_missing? if the method wasn't found.

Ruby implements this through its method lookup chain. The respond_to? method examines the object's method table, including methods from included modules and inherited classes. For dynamically generated methods handled by method_missing, Ruby relies on respond_to_missing? to indicate method availability.

class Calculator
  def add(a, b)
    a + b
  end
  
  def method_missing(name, *args)
    if name.to_s.start_with?('multiply_by_')
      factor = name.to_s.split('_').last.to_i
      args.first * factor
    else
      super
    end
  end
  
  def respond_to_missing?(name, include_private = false)
    name.to_s.start_with?('multiply_by_') || super
  end
end

calc = Calculator.new
calc.respond_to?(:add)              # => true
calc.respond_to?(:multiply_by_5)    # => true
calc.respond_to?(:divide)           # => false

The include_private parameter controls whether private methods are considered during the check. By default, respond_to? returns false for private methods unless explicitly requested.

class Example
  private
  
  def secret_method
    "hidden"
  end
end

obj = Example.new
obj.respond_to?(:secret_method)         # => false
obj.respond_to?(:secret_method, true)   # => true

Method introspection enables runtime decisions about object capabilities without catching exceptions. This approach supports flexible API design where different objects provide different interfaces while maintaining compatibility through duck typing.

Basic Usage

The most common usage pattern involves checking method availability before attempting method calls. This prevents NoMethodError exceptions and enables graceful fallback behavior.

def process_data(obj)
  if obj.respond_to?(:to_json)
    obj.to_json
  elsif obj.respond_to?(:to_s)
    obj.to_s
  else
    obj.inspect
  end
end

process_data([1, 2, 3])     # Uses to_json if available
process_data(Object.new)    # Falls back to inspect

Duck typing relies heavily on respond_to? to determine object capabilities at runtime. Objects don't need to share class hierarchies as long as they respond to the same method names.

def print_items(container)
  if container.respond_to?(:each)
    container.each { |item| puts item }
  else
    puts "Cannot iterate over #{container.class}"
  end
end

print_items([1, 2, 3])        # Array responds to each
print_items({a: 1, b: 2})     # Hash responds to each  
print_items("hello")          # String doesn't respond to each in this context

Conditional method calling becomes more readable when combined with the safe navigation operator, though respond_to? provides explicit control over method availability checks.

class OptionalFeature
  def initialize(enabled = false)
    @enabled = enabled
  end
  
  def advanced_feature
    "Advanced functionality" if @enabled
  end
  
  def respond_to_missing?(name, include_private = false)
    @enabled && name == :advanced_feature || super
  end
end

feature = OptionalFeature.new(true)
if feature.respond_to?(:advanced_feature)
  puts feature.advanced_feature
end

API wrappers commonly use respond_to? to validate method availability before forwarding calls to underlying objects. This pattern prevents unexpected errors when working with objects that might not implement complete interfaces.

class APIWrapper
  def initialize(client)
    @client = client
  end
  
  def method_missing(name, *args, &block)
    if @client.respond_to?(name)
      @client.send(name, *args, &block)
    else
      raise NoMethodError, "#{@client.class} doesn't support #{name}"
    end
  end
  
  def respond_to_missing?(name, include_private = false)
    @client.respond_to?(name, include_private) || super
  end
end

Method availability checks work with symbols and strings interchangeably, though symbols are more common and slightly more efficient due to Ruby's symbol table implementation.

obj = "example"
obj.respond_to?(:upcase)    # => true
obj.respond_to?("upcase")   # => true

Advanced Usage

Metaprogramming scenarios often require sophisticated respond_to_missing? implementations that handle complex dynamic method patterns. These implementations must carefully match the logic used in corresponding method_missing handlers.

class DynamicProxy
  def initialize(target)
    @target = target
    @intercepted_methods = {}
  end
  
  def intercept_method(pattern, &block)
    @intercepted_methods[pattern] = block
  end
  
  def method_missing(name, *args, &block)
    # Check intercepted patterns first
    @intercepted_methods.each do |pattern, handler|
      if name.to_s.match?(pattern)
        return handler.call(name, *args, &block)
      end
    end
    
    # Forward to target if it responds
    if @target.respond_to?(name, true)
      @target.send(name, *args, &block)
    else
      super
    end
  end
  
  def respond_to_missing?(name, include_private = false)
    # Must match method_missing logic exactly
    @intercepted_methods.keys.any? { |pattern| name.to_s.match?(pattern) } ||
      @target.respond_to?(name, include_private) ||
      super
  end
end

proxy = DynamicProxy.new([1, 2, 3])
proxy.intercept_method(/^find_/) do |method, *args|
  "Intercepted: #{method} with #{args}"
end

proxy.respond_to?(:find_something)  # => true
proxy.respond_to?(:length)          # => true (forwarded to Array)
proxy.respond_to?(:unknown)         # => false

Module inclusion patterns require careful consideration when implementing respond_to_missing?. The method must account for modules that might be included after the initial definition.

module Trackable
  def method_missing(name, *args, &block)
    if name.to_s.start_with?('track_')
      event_name = name.to_s.sub('track_', '')
      log_event(event_name, args)
    else
      super
    end
  end
  
  def respond_to_missing?(name, include_private = false)
    name.to_s.start_with?('track_') || super
  end
  
  private
  
  def log_event(event, data)
    puts "Tracked: #{event} with #{data}"
  end
end

class User
  include Trackable
  
  def name
    @name
  end
end

user = User.new
user.respond_to?(:track_login)      # => true
user.respond_to?(:track_purchase)   # => true
user.track_login('user123')         # Works through method_missing

Inheritance hierarchies with multiple respond_to_missing? implementations require careful super calling to ensure all levels of the hierarchy participate in method resolution.

class BaseService
  def respond_to_missing?(name, include_private = false)
    name.to_s.end_with?('_service') || super
  end
  
  def method_missing(name, *args, &block)
    if name.to_s.end_with?('_service')
      "Base service: #{name}"
    else
      super
    end
  end
end

class ExtendedService < BaseService
  def respond_to_missing?(name, include_private = false)
    name.to_s.start_with?('extended_') || super
  end
  
  def method_missing(name, *args, &block)
    if name.to_s.start_with?('extended_')
      "Extended: #{name}"
    else
      super
    end
  end
end

service = ExtendedService.new
service.respond_to?(:auth_service)      # => true (from BaseService)
service.respond_to?(:extended_feature)  # => true (from ExtendedService)
service.respond_to?(:unknown)           # => false

Performance-sensitive applications can implement caching strategies within respond_to_missing? to avoid repeated pattern matching for frequently checked methods.

class CachedDynamicObject
  def initialize
    @respond_to_cache = {}
  end
  
  def respond_to_missing?(name, include_private = false)
    cache_key = [name, include_private]
    return @respond_to_cache[cache_key] if @respond_to_cache.key?(cache_key)
    
    result = expensive_method_check(name) || super
    @respond_to_cache[cache_key] = result
  end
  
  private
  
  def expensive_method_check(name)
    # Simulate expensive pattern matching or API calls
    name.to_s.match?(/^dynamic_\w+_\d+$/)
  end
end

Common Pitfalls

The most frequent mistake involves inconsistency between method_missing and respond_to_missing? implementations. These methods must use identical logic to determine method availability, or Ruby's introspection will provide incorrect information.

# WRONG: Inconsistent logic
class BadExample
  def method_missing(name, *args, &block)
    if name.to_s.start_with?('get_') && args.empty?
      "getter: #{name}"
    else
      super
    end
  end
  
  def respond_to_missing?(name, include_private = false)
    # BUG: Doesn't check args.empty? like method_missing does
    name.to_s.start_with?('get_') || super
  end
end

bad = BadExample.new
bad.respond_to?(:get_value)     # => true
bad.get_value("arg")            # Raises NoMethodError despite respond_to? returning true
# CORRECT: Consistent logic
class GoodExample
  def method_missing(name, *args, &block)
    if valid_getter?(name, args)
      "getter: #{name}"
    else
      super
    end
  end
  
  def respond_to_missing?(name, include_private = false)
    name.to_s.start_with?('get_') || super
  end
  
  private
  
  def valid_getter?(name, args)
    name.to_s.start_with?('get_') && args.empty?
  end
end

Forgetting to call super in respond_to_missing? breaks the method lookup chain and can hide methods that should be available through inheritance or module inclusion.

class WithoutSuper
  def respond_to_missing?(name, include_private = false)
    name == :custom_method
    # BUG: Missing || super
  end
end

class WithSuper
  def respond_to_missing?(name, include_private = false)
    name == :custom_method || super
  end
end

# WithoutSuper breaks normal method resolution
# WithSuper preserves it correctly

Private method handling creates confusion when the include_private parameter isn't properly propagated through the inheritance chain or when developers misunderstand its behavior.

class PrivateMethodExample
  def respond_to_missing?(name, include_private = false)
    if name == :dynamic_private
      include_private  # Only return true if private methods are requested
    else
      super
    end
  end
  
  def method_missing(name, *args, &block)
    if name == :dynamic_private
      "private dynamic method"
    else
      super
    end
  end
  
  private :method_missing  # This doesn't make dynamic methods private
end

obj = PrivateMethodExample.new
obj.respond_to?(:dynamic_private)        # => false
obj.respond_to?(:dynamic_private, true)  # => true

Method name conversion between strings and symbols can cause subtle bugs when implementations don't handle both formats consistently.

class StringSymbolBug
  def respond_to_missing?(name, include_private = false)
    # BUG: Only checks symbol format
    name == :dynamic_method || super
  end
  
  def method_missing(name, *args, &block)
    # BUG: Converts string to symbol but respond_to_missing? doesn't
    if name.to_s == "dynamic_method"
      "found it"
    else
      super
    end
  end
end

buggy = StringSymbolBug.new
buggy.respond_to?("dynamic_method")  # => false, should be true
buggy.dynamic_method                 # => "found it", works despite respond_to? saying false

Performance issues arise when respond_to_missing? implementations perform expensive operations without caching, especially in code paths that call respond_to? frequently.

# SLOW: Expensive operations on every check
class SlowResponder
  def respond_to_missing?(name, include_private = false)
    # Don't do this: expensive regex compilation on every call
    name.to_s.match?(/^api_\w+_v\d+_\w+$/) || super
  end
end

# BETTER: Pre-compile patterns or cache results
class FastResponder
  PATTERN = /^api_\w+_v\d+_\w+$/.freeze
  
  def respond_to_missing?(name, include_private = false)
    name.to_s.match?(PATTERN) || super
  end
end

Reference

Core Methods

Method Parameters Returns Description
#respond_to?(symbol, include_all = false) symbol (Symbol/String), include_all (Boolean) Boolean Checks if object responds to the given method name
#respond_to_missing?(symbol, include_private) symbol (Symbol/String), include_private (Boolean) Boolean Hook for indicating dynamic method availability

Method Parameters

Parameter Type Default Description
symbol Symbol, String Required Method name to check
include_all Boolean false Include private and protected methods in check
include_private Boolean Required Include private methods in dynamic method check

Return Values

Value Condition Meaning
true Method exists or is dynamically available Object can respond to the method call
false Method doesn't exist Object cannot respond to the method call

Method Lookup Order

Step Check Action
1 Explicit method definition Return true if method exists in object's method table
2 Ancestor chain Check included modules and parent classes
3 respond_to_missing? Call hook method for dynamic method availability
4 Default behavior Return false if none of above match

Common Patterns

# Basic availability check
obj.respond_to?(:method_name)

# Include private methods
obj.respond_to?(:method_name, true)

# Dynamic method implementation
def respond_to_missing?(name, include_private = false)
  pattern_matches?(name) || super
end

def method_missing(name, *args, &block)
  if pattern_matches?(name)
    handle_dynamic_method(name, *args, &block)
  else
    super
  end
end

# Proxy forwarding
def respond_to_missing?(name, include_private = false)
  @target.respond_to?(name, include_private) || super
end

Error Conditions

Scenario Exception Solution
Inconsistent method_missing/respond_to_missing? NoMethodError Ensure identical logic in both methods
Missing super call Method resolution breaks Always call super in respond_to_missing?
Expensive pattern matching Performance degradation Cache pattern compilation or results
String/Symbol mismatch Inconsistent behavior Handle both formats consistently