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 |